@SuppressWarnings("checkstyle:ConstantName")
public static final String uriName = "odl-pretty-print";
+ public static final @NonNull PrettyPrintParam FALSE = new PrettyPrintParam(false);
+ public static final @NonNull PrettyPrintParam TRUE = new PrettyPrintParam(true);
+
private static final @NonNull URI CAPABILITY =
URI.create("urn:opendaylight:params:restconf:capability:pretty-print:1.0");
- private static final @NonNull PrettyPrintParam FALSE = new PrettyPrintParam(false);
- private static final @NonNull PrettyPrintParam TRUE = new PrettyPrintParam(true);
private final boolean value;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.restconf.server.api.ReplyBody;
+import org.opendaylight.restconf.server.api.FormattableBody;
-abstract sealed class ReplyBodyWriter implements MessageBodyWriter<ReplyBody>
- permits JsonReplyBodyWriter, XmlReplyBodyWriter {
+abstract sealed class FormattableBodyWriter implements MessageBodyWriter<FormattableBody>
+ permits JsonFormattableBody, XmlFormattableBody {
@Override
public final boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
final MediaType mediaType) {
- return ReplyBody.class.isAssignableFrom(type);
+ return FormattableBody.class.isAssignableFrom(type);
}
@Override
- public final void writeTo(final ReplyBody body, final Class<?> type, final Type genericType,
+ public final void writeTo(final FormattableBody body, final Class<?> type, final Type genericType,
final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException {
writeTo(requireNonNull(body), requireNonNull(entityStream));
}
- abstract void writeTo(@NonNull ReplyBody body, @NonNull OutputStream out) throws IOException;
+ abstract void writeTo(@NonNull FormattableBody body, @NonNull OutputStream out) throws IOException;
}
import org.opendaylight.restconf.server.api.DataPatchResult;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
-import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
import org.opendaylight.restconf.server.api.DataPutResult;
import org.opendaylight.restconf.server.api.DataYangPatchResult;
+import org.opendaylight.restconf.server.api.InvokeResult;
import org.opendaylight.restconf.server.api.JsonChildBody;
import org.opendaylight.restconf.server.api.JsonDataPostBody;
import org.opendaylight.restconf.server.api.JsonOperationInputBody;
import org.opendaylight.restconf.server.api.JsonResourceBody;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.OperationInputBody;
-import org.opendaylight.restconf.server.api.OperationOutputBody;
import org.opendaylight.restconf.server.api.OperationsGetResult;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.api.RestconfServer;
import org.opendaylight.restconf.server.api.XmlChildBody;
import org.opendaylight.restconf.server.api.XmlDataPostBody;
fillConfigurationMetadata(builder, createResource);
return builder.build();
}
- if (result instanceof InvokeOperation invokeOperation) {
+ if (result instanceof InvokeResult invokeOperation) {
final var output = invokeOperation.output();
return output == null ? Response.status(Status.NO_CONTENT).build()
: Response.status(Status.OK).entity(output).build();
private void operationsPOST(final ApiPath identifier, final UriInfo uriInfo, final AsyncResponse ar,
final OperationInputBody body) {
- server.operationsPOST(uriInfo.getBaseUri(), identifier, body)
- .addCallback(new JaxRsRestconfCallback<OperationsPostResult>(ar) {
+ server.operationsPOST(uriInfo.getBaseUri(), identifier, QueryParams.normalize(uriInfo), body)
+ .addCallback(new JaxRsRestconfCallback<>(ar) {
@Override
- Response transform(final OperationsPostResult result) {
+ Response transform(final InvokeResult result) {
final var body = result.output();
return body == null ? Response.noContent().build()
- : Response.ok().entity(new OperationOutputBody(result.path(), body, false)).build();
+ : Response.ok().entity(body).build();
}
});
}
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import org.opendaylight.restconf.api.MediaTypes;
-import org.opendaylight.restconf.server.api.ReplyBody;
+import org.opendaylight.restconf.server.api.FormattableBody;
@Provider
@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
-public final class JsonReplyBodyWriter extends ReplyBodyWriter {
+public final class JsonFormattableBody extends FormattableBodyWriter {
@Override
- void writeTo(final ReplyBody body, final OutputStream out) throws IOException {
- body.writeJSON(out);
+ void writeTo(final FormattableBody body, final OutputStream out) throws IOException {
+ body.formatToJSON(out);
}
}
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import org.opendaylight.restconf.api.MediaTypes;
-import org.opendaylight.restconf.server.api.ReplyBody;
+import org.opendaylight.restconf.server.api.FormattableBody;
@Provider
@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
-public final class XmlReplyBodyWriter extends ReplyBodyWriter {
+public final class XmlFormattableBody extends FormattableBodyWriter {
@Override
- void writeTo(final ReplyBody body, final OutputStream out) throws IOException {
- body.writeXML(out);
+ void writeTo(final FormattableBody body, final OutputStream out) throws IOException {
+ body.formatToXML(out);
}
}
package org.opendaylight.restconf.nb.rfc8040;
import static java.util.Objects.requireNonNull;
-import static org.opendaylight.restconf.server.api.EventStreamGetParams.optionalParam;
+import static org.opendaylight.restconf.server.api.EventStreamGetParams.mandatoryParam;
import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
switch (paramName) {
case InsertParam.uriName:
- insert = optionalParam(InsertParam::forUriValue, paramName, paramValue);
+ insert = mandatoryParam(InsertParam::forUriValue, paramName, paramValue);
break;
case PointParam.uriName:
- point = optionalParam(PointParam::forUriValue, paramName, paramValue);
+ point = mandatoryParam(PointParam::forUriValue, paramName, paramValue);
break;
default:
throw new IllegalArgumentException("Invalid parameter: " + paramName);
import org.opendaylight.aaa.web.servlet.ServletSupport;
import org.opendaylight.restconf.nb.jaxrs.JaxRsRestconf;
import org.opendaylight.restconf.nb.jaxrs.JaxRsWebHostMetadata;
-import org.opendaylight.restconf.nb.jaxrs.JsonReplyBodyWriter;
-import org.opendaylight.restconf.nb.jaxrs.XmlReplyBodyWriter;
+import org.opendaylight.restconf.nb.jaxrs.JsonFormattableBody;
+import org.opendaylight.restconf.nb.jaxrs.XmlFormattableBody;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonPatchStatusBodyWriter;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBodyWriter;
@Override
public Set<Object> getSingletons() {
return Set.of(
- new JsonReplyBodyWriter(), new XmlReplyBodyWriter(),
+ new JsonFormattableBody(), new XmlFormattableBody(),
new RestconfDocumentedExceptionMapper(databindProvider),
new JaxRsRestconf(server));
}
DepthParam depth = null;
FieldsParam fields = null;
WithDefaultsParam withDefaults = null;
- PrettyPrintParam prettyPrint = null;
+ PrettyPrintParam prettyPrint = PrettyPrintParam.FALSE;
for (var entry : uriInfo.getQueryParameters().entrySet()) {
final var paramName = entry.getKey();
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.MediaTypes;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.restconf.server.api.FormattableBody;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
@Provider
@Produces({ MediaTypes.APPLICATION_YANG_DATA_JSON, MediaType.APPLICATION_JSON })
public final class JsonNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
- private static final int DEFAULT_INDENT_SPACES_NUM = 2;
-
@Override
void writeData(final SchemaInferenceStack stack, final QueryParameters writerParameters, final NormalizedNode data,
final OutputStream entityStream) throws IOException {
.build()
: data;
- try (var jsonWriter = createJsonWriter(entityStream, writerParameters.prettyPrint())) {
+ try (var jsonWriter = FormattableBody.createJsonWriter(entityStream, writerParameters)) {
jsonWriter.beginObject();
final var nnWriter = createNormalizedNodeWriter(stack.toInference(), jsonWriter, writerParameters, null);
JSONNormalizedNodeStreamWriter.createNestedWriter(codecs, inference,
initialNamespace, jsonWriter), writerParameters.depth(), writerParameters.fields());
}
-
- private static JsonWriter createJsonWriter(final OutputStream entityStream,
- final @Nullable PrettyPrintParam prettyPrint) {
- final var outputWriter = new OutputStreamWriter(entityStream, StandardCharsets.UTF_8);
- return prettyPrint != null && prettyPrint.value()
- ? JsonWriterFactory.createJsonWriter(outputWriter, DEFAULT_INDENT_SPACES_NUM)
- : JsonWriterFactory.createJsonWriter(outputWriter);
- }
}
import java.io.IOException;
import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import javanet.staxutils.IndentingXMLStreamWriter;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import javax.xml.XMLConstants;
-import javax.xml.stream.FactoryConfigurationError;
-import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
-import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.MediaTypes;
-import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.restconf.server.api.FormattableBody;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
@Provider
@Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
public final class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
- private static final XMLOutputFactory XML_FACTORY;
-
- static {
- XML_FACTORY = XMLOutputFactory.newFactory();
- XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
- }
-
@Override
void writeData(final SchemaInferenceStack stack, final QueryParameters writerParameters, final NormalizedNode data,
final OutputStream entityStream) throws IOException {
isRoot = true;
}
- final var xmlWriter = createXmlWriter(entityStream, writerParameters.prettyPrint());
+ final var xmlWriter = FormattableBody.createXmlWriter(entityStream, writerParameters);
final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), writerParameters);
if (data instanceof MapEntryNode mapEntry) {
// Restconf allows returning one list item. We need to wrap it
nnWriter.flush();
}
- private static XMLStreamWriter createXmlWriter(final OutputStream entityStream,
- final @Nullable PrettyPrintParam prettyPrint) throws IOException {
- final XMLStreamWriter xmlWriter;
- try {
- xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
- } catch (XMLStreamException | FactoryConfigurationError e) {
- throw new IOException(e);
- }
-
- return prettyPrint != null && prettyPrint.value() ? new IndentingXMLStreamWriter(xmlWriter) : xmlWriter;
- }
-
private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
final Inference inference, final QueryParameters writerParameters) {
return ParameterAwareNormalizedNodeWriter.forStreamWriter(
*/
package org.opendaylight.restconf.nb.rfc8040.legacy;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.annotations.Beta;
import java.util.List;
import java.util.Set;
import org.opendaylight.restconf.api.query.DepthParam;
import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.server.api.DataGetParams;
+import org.opendaylight.restconf.server.api.FormatParameters;
import org.opendaylight.yangtools.yang.common.QName;
/**
// FIXME: this probably needs to be renamed back to WriterParams, or somesuch
public record QueryParameters(
@Nullable DepthParam depth,
- @Nullable PrettyPrintParam prettyPrint,
- @Nullable List<Set<QName>> fields) {
- public static final @NonNull QueryParameters EMPTY = new QueryParameters(null, null, null);
+ @NonNull PrettyPrintParam prettyPrint,
+ @Nullable List<Set<QName>> fields) implements FormatParameters {
+ public static final @NonNull QueryParameters EMPTY = new QueryParameters(null, PrettyPrintParam.FALSE, null);
+
+ public QueryParameters {
+ requireNonNull(prettyPrint);
+ }
public static @NonNull QueryParameters of(final DataGetParams params) {
final var depth = params.depth();
final var prettyPrint = params.prettyPrint();
- return depth == null && prettyPrint == null ? EMPTY : new QueryParameters(depth, prettyPrint, null);
+ return depth == null && !prettyPrint.value() ? EMPTY : new QueryParameters(depth, prettyPrint, null);
}
public static @NonNull QueryParameters of(final DataGetParams params, final List<Set<QName>> fields) {
import org.opendaylight.restconf.server.api.DataPostBody;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
-import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
import org.opendaylight.restconf.server.api.DataPutResult;
import org.opendaylight.restconf.server.api.DataYangPatchResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.DatabindPath.Action;
import org.opendaylight.restconf.server.api.DatabindPath.Data;
import org.opendaylight.restconf.server.api.DatabindPath.InstanceReference;
+import org.opendaylight.restconf.server.api.DatabindPath.OperationPath;
import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
+import org.opendaylight.restconf.server.api.InvokeParams;
+import org.opendaylight.restconf.server.api.InvokeResult;
import org.opendaylight.restconf.server.api.OperationInputBody;
import org.opendaylight.restconf.server.api.OperationOutputBody;
import org.opendaylight.restconf.server.api.OperationsGetResult;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.api.PatchBody;
import org.opendaylight.restconf.server.api.ResourceBody;
import org.opendaylight.restconf.server.spi.ApiPathCanonizer;
return RestconfFuture.of(new OperationsGetResult.Leaf(rpc.inference().modelContext(), rpc.rpc().argument()));
}
- public @NonNull RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
- final OperationInputBody body) {
+ public @NonNull RestconfFuture<InvokeResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
+ final Map<String, String> queryParameters, final OperationInputBody body) {
final Rpc path;
try {
path = pathNormalizer.normalizeRpcPath(apiPath);
return RestconfFuture.failed(e);
}
+ final InvokeParams params;
+ try {
+ params = InvokeParams.ofQueryParameters(queryParameters);
+ } catch (IllegalArgumentException e) {
+ return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
+ }
+
final ContainerNode data;
try {
data = body.toContainerNode(path);
final var type = path.rpc().argument();
final var local = localRpcs.get(type);
if (local != null) {
- return local.invoke(restconfURI, new OperationInput(path, data));
+ return local.invoke(restconfURI, new OperationInput(path, data))
+ .transform(output -> outputToInvokeResult(path, params, output));
}
if (rpcService == null) {
LOG.debug("RPC invocation is not available");
ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED));
}
- final var ret = new SettableRestconfFuture<OperationsPostResult>();
+ final var ret = new SettableRestconfFuture<InvokeResult>();
Futures.addCallback(rpcService.invokeRpc(type, data), new FutureCallback<DOMRpcResult>() {
@Override
public void onSuccess(final DOMRpcResult response) {
final var errors = response.errors();
if (errors.isEmpty()) {
- ret.set(new OperationsPostResult(path, response.value()));
+ ret.set(outputToInvokeResult(path, params, response.value()));
} else {
LOG.debug("RPC invocation reported {}", response.errors());
ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null,
return ret;
}
+ private static @NonNull InvokeResult outputToInvokeResult(final @NonNull OperationPath path,
+ final @NonNull InvokeParams params, final @Nullable ContainerNode value) {
+ return value == null || value.isEmpty() ? InvokeResult.EMPTY
+ : new InvokeResult(new OperationOutputBody(params, path, value));
+ }
+
public @NonNull RestconfFuture<CharSource> resolveSource(final SourceIdentifier source,
final Class<? extends SourceRepresentation> representation) {
final var src = requireNonNull(source);
}
if (path instanceof Action actionPath) {
try (var inputBody = body.toOperationInput()) {
- return dataInvokePOST(actionPath, inputBody);
+ return dataInvokePOST(actionPath, inputBody, queryParameters);
}
}
// Note: this should never happen
return ret;
}
- private @NonNull RestconfFuture<InvokeOperation> dataInvokePOST(final @NonNull Action path,
- final @NonNull OperationInputBody body) {
+ private @NonNull RestconfFuture<InvokeResult> dataInvokePOST(final @NonNull Action path,
+ final @NonNull OperationInputBody body, final Map<String, String> queryParameters) {
+ final InvokeParams params;
+ try {
+ params = InvokeParams.ofQueryParameters(queryParameters);
+ } catch (IllegalArgumentException e) {
+ return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
+ }
+
final ContainerNode input;
try {
input = body.toContainerNode(path);
return RestconfFuture.failed(new RestconfDocumentedException("DOMActionService is missing."));
}
- final var future = dataInvokePOST(actionService, path, input);
- return future.transform(result -> result.getOutput()
- .flatMap(output -> output.isEmpty() ? Optional.empty()
- : Optional.of(new InvokeOperation(new OperationOutputBody(path, output, false))))
- .orElse(InvokeOperation.EMPTY));
+ return dataInvokePOST(actionService, path, input)
+ .transform(result -> outputToInvokeResult(path, params, result.getOutput().orElse(null)));
}
/**
import org.opendaylight.restconf.api.query.FieldsParam;
import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
-import org.opendaylight.yangtools.concepts.Immutable;
/**
* Supported query parameters of {@code /data} {@code GET} HTTP operation, as defined in
@Nullable DepthParam depth,
@Nullable FieldsParam fields,
@Nullable WithDefaultsParam withDefaults,
- @Nullable PrettyPrintParam prettyPrint) implements Immutable {
- public static final @NonNull DataGetParams EMPTY = new DataGetParams(ContentParam.ALL, null, null, null, null);
+ @NonNull PrettyPrintParam prettyPrint) implements FormatParameters {
+ public static final @NonNull DataGetParams EMPTY =
+ new DataGetParams(ContentParam.ALL, null, null, null, PrettyPrintParam.FALSE);
public DataGetParams {
requireNonNull(content);
+ requireNonNull(prettyPrint);
}
}
* Result of a {@code POST} request as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4">RFC8040 section 4.4</a>.
*/
-public sealed interface DataPostResult {
+public sealed interface DataPostResult permits DataPostResult.CreateResource, InvokeResult {
/**
* Result of a {@code POST} request in as defined in
* <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4.1">RFC8040 Create Resource Mode</a>.
this(createdPath, null, null);
}
}
-
- /**
- * Result of a {@code POST} request as defined in
- * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4.2">RFC8040 Invoke Operation Mode</a>.
- *
- * @param output Non-empty operation output, or {@code null}
- */
- record InvokeOperation(@Nullable OperationOutputBody output) implements DataPostResult {
- public static final @NonNull InvokeOperation EMPTY = new InvokeOperation(null);
- }
}
import java.util.Map;
import java.util.function.Function;
import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
import org.opendaylight.restconf.api.query.FilterParam;
switch (paramName) {
case FilterParam.uriName:
- filter = optionalParam(FilterParam::forUriValue, paramName, paramValue);
+ filter = mandatoryParam(FilterParam::forUriValue, paramName, paramValue);
break;
case StartTimeParam.uriName:
- startTime = optionalParam(StartTimeParam::forUriValue, paramName, paramValue);
+ startTime = mandatoryParam(StartTimeParam::forUriValue, paramName, paramValue);
break;
case StopTimeParam.uriName:
- stopTime = optionalParam(StopTimeParam::forUriValue, paramName, paramValue);
+ stopTime = mandatoryParam(StopTimeParam::forUriValue, paramName, paramValue);
break;
case LeafNodesOnlyParam.uriName:
- leafNodesOnly = optionalParam(LeafNodesOnlyParam::forUriValue, paramName, paramValue);
+ leafNodesOnly = mandatoryParam(LeafNodesOnlyParam::forUriValue, paramName, paramValue);
break;
case SkipNotificationDataParam.uriName:
- skipNotificationData = optionalParam(SkipNotificationDataParam::forUriValue, paramName,
+ skipNotificationData = mandatoryParam(SkipNotificationDataParam::forUriValue, paramName,
paramValue);
break;
case ChangedLeafNodesOnlyParam.uriName:
- changedLeafNodesOnly = optionalParam(ChangedLeafNodesOnlyParam::forUriValue, paramName,
+ changedLeafNodesOnly = mandatoryParam(ChangedLeafNodesOnlyParam::forUriValue, paramName,
paramValue);
break;
case ChildNodesOnlyParam.uriName:
- childNodesOnly = optionalParam(ChildNodesOnlyParam::forUriValue, paramName, paramValue);
+ childNodesOnly = mandatoryParam(ChildNodesOnlyParam::forUriValue, paramName, paramValue);
break;
default:
throw new IllegalArgumentException("Invalid parameter: " + paramName);
}
// FIXME: find a better place for this method
- public static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
+ public static <T> @NonNull T mandatoryParam(final Function<String, @NonNull T> factory, final String name,
final String value) {
try {
return factory.apply(requireNonNull(value));
--- /dev/null
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.server.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
+import org.opendaylight.restconf.api.query.RestconfQueryParam;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * The set of {@link RestconfQueryParam}s governing output formatting.
+ */
+@NonNullByDefault
+public interface FormatParameters extends Immutable {
+ /**
+ * Return the {@link PrettyPrintParam} parameter.
+ *
+ * @return the {@link PrettyPrintParam} parameter
+ */
+ PrettyPrintParam prettyPrint();
+}
* to an {@link OutputStream}.
*/
@NonNullByDefault
-public abstract class ReplyBody implements Immutable {
+public abstract class FormattableBody implements Immutable {
private static final XMLOutputFactory XML_FACTORY = XMLOutputFactory.newFactory();
private static final String PRETTY_PRINT_INDENT = " ";
- private final boolean prettyPrint;
+ private final FormatParameters format;
- ReplyBody(final boolean prettyPrint) {
- this.prettyPrint = prettyPrint;
+ FormattableBody(final FormatParameters format) {
+ this.format = requireNonNull(format);
}
/**
* @param out output stream
* @throws IOException if an IO error occurs.
*/
- public final void writeJSON(final OutputStream out) throws IOException {
- writeJSON(requireNonNull(out), prettyPrint);
+ public final void formatToJSON(final OutputStream out) throws IOException {
+ formatToJSON(requireNonNull(out), format);
}
- abstract void writeJSON(OutputStream out, boolean prettyPrint) throws IOException;
+ abstract void formatToJSON(OutputStream out, FormatParameters format) throws IOException;
/**
* Write the content of this body as an XML document.
* @param out output stream
* @throws IOException if an IO error occurs.
*/
- public final void writeXML(final OutputStream out) throws IOException {
- writeXML(requireNonNull(out), prettyPrint);
+ public final void formatToXML(final OutputStream out) throws IOException {
+ formatToXML(requireNonNull(out), format);
}
- abstract void writeXML(OutputStream out, boolean prettyPrint) throws IOException;
+ abstract void formatToXML(OutputStream out, FormatParameters format) throws IOException;
@Override
public final String toString() {
}
ToStringHelper addToStringAttributes(final ToStringHelper helper) {
- return helper.add("prettyPrint", prettyPrint);
+ return helper.add("prettyPrint", format.prettyPrint().value());
}
- static final JsonWriter createJsonWriter(final OutputStream out, final boolean prettyPrint) {
+ public static final JsonWriter createJsonWriter(final OutputStream out, final FormatParameters format) {
final var ret = JsonWriterFactory.createJsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
- ret.setIndent(prettyPrint ? PRETTY_PRINT_INDENT : "");
+ ret.setIndent(format.prettyPrint().value() ? PRETTY_PRINT_INDENT : "");
return ret;
}
- static final XMLStreamWriter createXmlWriter(final OutputStream out, final boolean prettyPrint) throws IOException {
+ public static final XMLStreamWriter createXmlWriter(final OutputStream out, final FormatParameters format)
+ throws IOException {
final var xmlWriter = createXmlWriter(out);
- return prettyPrint ? new IndentingXMLStreamWriter(xmlWriter) : xmlWriter;
+ return format.prettyPrint().value() ? new IndentingXMLStreamWriter(xmlWriter) : xmlWriter;
}
private static XMLStreamWriter createXmlWriter(final OutputStream out) throws IOException {
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.server.api;
+
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.restconf.server.api.EventStreamGetParams.mandatoryParam;
+
+import java.util.Map;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.restconf.api.query.PrettyPrintParam;
+
+/**
+ * Supported query parameters of {@code POST} HTTP operation when the request is to invoke a YANG operation, be it
+ * {@code rpc} or {@code action}. There is no such thing in RFC8040, but we support pretty-printing of the resulting
+ * {@code output} container.
+ */
+public record InvokeParams(@NonNull PrettyPrintParam prettyPrint) implements FormatParameters {
+ public static final @NonNull InvokeParams EMPTY = new InvokeParams(PrettyPrintParam.FALSE);
+
+ public InvokeParams {
+ requireNonNull(prettyPrint);
+ }
+
+ /**
+ * Return {@link InvokeParams} for specified query parameters.
+ *
+ * @param queryParameters Parameters and their values
+ * @return A {@link InvokeParams}
+ * @throws NullPointerException if {@code queryParameters} is {@code null}
+ * @throws IllegalArgumentException if the parameters are invalid
+ */
+ public static @NonNull InvokeParams ofQueryParameters(final Map<String, String> queryParameters) {
+ if (queryParameters.isEmpty()) {
+ return EMPTY;
+ }
+
+ PrettyPrintParam prettyPrint = PrettyPrintParam.FALSE;
+
+ for (var entry : queryParameters.entrySet()) {
+ final var paramName = entry.getKey();
+ final var paramValue = entry.getValue();
+
+ prettyPrint = switch (paramName) {
+ case PrettyPrintParam.uriName -> mandatoryParam(PrettyPrintParam::forUriValue, paramName, paramValue);
+ default -> throw new IllegalArgumentException("Invalid parameter: " + paramName);
+ };
+ }
+
+ return new InvokeParams(requireNonNull(prettyPrint));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.server.api;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Result of a {@code POST} request resulting in an operation invocation, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.6">RFC8040 Operation Resource</a> and
+ * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4.2">RFC8040 Invoke Operation Mode</a>.
+ *
+ * @param output Non-empty operation output, or {@code null}
+ */
+public record InvokeResult(@Nullable OperationOutputBody output) implements DataPostResult {
+ /**
+ * Empty instance. Prefer this to creating one with {@code output}.
+ */
+ public static final @NonNull InvokeResult EMPTY = new InvokeResult(null);
+}
\ No newline at end of file
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects.ToStringHelper;
import java.io.IOException;
import java.io.OutputStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
/**
- * A {@link ReplyBody} corresponding to a {@code rpc} or {@code action} invocation.
+ * A {@link FormattableBody} corresponding to a {@code rpc} or {@code action} invocation.
*/
@NonNullByDefault
-public final class OperationOutputBody extends ReplyBody {
+public final class OperationOutputBody extends FormattableBody {
private final OperationPath path;
private final ContainerNode output;
- public OperationOutputBody(final OperationPath path, final ContainerNode output, final boolean prettyPrint) {
- super(prettyPrint);
+ public OperationOutputBody(final FormatParameters format, final OperationPath path, final ContainerNode output) {
+ super(format);
this.path = requireNonNull(path);
this.output = requireNonNull(output);
if (output.isEmpty()) {
}
@Override
- void writeJSON(final OutputStream out, final boolean prettyPrint) throws IOException {
+ void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException {
final var stack = prepareStack();
// RpcDefinition/ActionDefinition is not supported as initial codec in JSONStreamWriter, so we need to emit
// initial output declaration
- try (var jsonWriter = createJsonWriter(out, prettyPrint)) {
+ try (var jsonWriter = createJsonWriter(out, format)) {
final var module = stack.currentModule();
jsonWriter.beginObject().name(module.argument().getLocalName() + ":output").beginObject();
}
@Override
- void writeXML(final OutputStream out, final boolean prettyPrint) throws IOException {
+ void formatToXML(final OutputStream out, final FormatParameters format) throws IOException {
final var stack = prepareStack();
// RpcDefinition/ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit
// initial output declaration.
- final var xmlWriter = createXmlWriter(out, prettyPrint);
+ final var xmlWriter = createXmlWriter(out, format);
final var nnWriter = ParameterAwareNormalizedNodeWriter.forStreamWriter(
XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, stack.toInference()), null, null);
nnWriter.flush();
}
+ @Override
+ ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+ return super.addToStringAttributes(helper.add("path", path).add("output", output.prettyTree()));
+ }
+
private SchemaInferenceStack prepareStack() {
final var stack = path.inference().toSchemaInferenceStack();
stack.enterSchemaTree(path.outputStatement().argument());
+++ /dev/null
-/*
- * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.restconf.server.api;
-
-import static java.util.Objects.requireNonNull;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.server.api.DatabindPath.OperationPath;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-
-/**
- * RESTCONF {@code /operations} content for a {@code POST} operation as per
- * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.6">RFC8040 Operation Resource</a> and
- * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4.2">RFC8040 Invoke Operation Mode</a>.
- *
- * @param path associated {@link OperationPath}
- * @param output Operation output, or {@code null} if output would be empty
- */
-public record OperationsPostResult(@NonNull OperationPath path, @Nullable ContainerNode output) {
- public OperationsPostResult {
- requireNonNull(path);
- if (output != null && output.isEmpty()) {
- output = null;
- }
- }
-}
\ No newline at end of file
RestconfFuture<DataPutResult> dataPUT(ApiPath identifier, ResourceBody body, Map<String, String> queryParameters);
/**
- * Return the set of supported RPCs supported by {@link #operationsPOST(URI, ApiPath, OperationInputBody)},
+ * Return the set of supported RPCs supported by {@link #operationsPOST(URI, ApiPath, Map, OperationInputBody)},
* as expressed in the <a href="https://www.rfc-editor.org/rfc/rfc8040#page-84">ietf-restconf.yang</a>
* {@code container operations} statement.
*
*
* @param restconfURI Base URI of the request
* @param operation {@code <operation>} path, really an {@link ApiPath} to an {@code rpc}
+ * @param queryParameters query parameters
* @param body RPC operation
- * @return A {@link RestconfFuture} completing with {@link OperationsPostResult}
+ * @return A {@link RestconfFuture} completing with {@link InvokeResult}
*/
// FIXME: 'operation' should really be an ApiIdentifier with non-null module, but we also support yang-ext:mount,
// and hence it is a path right now
- RestconfFuture<OperationsPostResult> operationsPOST(URI restconfURI, ApiPath operation, OperationInputBody body);
+ RestconfFuture<InvokeResult> operationsPOST(URI restconfURI, ApiPath operation, Map<String, String> queryParameters,
+ OperationInputBody body);
/**
* Return the revision of {@code ietf-yang-library} module implemented by this server, as defined in
import org.opendaylight.restconf.server.api.DataPutResult;
import org.opendaylight.restconf.server.api.DataYangPatchResult;
import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.InvokeResult;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.OperationInputBody;
import org.opendaylight.restconf.server.api.OperationsGetResult;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.api.PatchBody;
import org.opendaylight.restconf.server.api.ResourceBody;
import org.opendaylight.restconf.server.api.RestconfServer;
}
@Override
- public RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
- final OperationInputBody body) {
+ public RestconfFuture<InvokeResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
+ final Map<String, String> queryParameters, final OperationInputBody body) {
final StrategyAndTail strategyAndTail;
try {
strategyAndTail = localStrategy().resolveStrategy(apiPath);
return RestconfFuture.failed(e);
}
final var strategy = strategyAndTail.strategy();
- return strategy.operationsPOST(restconfURI, strategyAndTail.tail(), body);
+ return strategy.operationsPOST(restconfURI, strategyAndTail.tail(), queryParameters, body);
}
@Override
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.spi.ApiPathCanonizer;
import org.opendaylight.restconf.server.spi.OperationInput;
import org.opendaylight.restconf.server.spi.RestconfStream;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
}
@Override
- public RestconfFuture<OperationsPostResult> invoke(final URI restconfURI, final OperationInput input) {
+ public RestconfFuture<ContainerNode> invoke(final URI restconfURI, final OperationInput input) {
final var body = input.input();
final var pathLeaf = body.childByArg(DEVICE_NOTIFICATION_PATH_NODEID);
if (pathLeaf == null) {
ErrorType.APPLICATION, ErrorTag.INVALID_VALUE));
}
- final var operPath = input.path();
-
return streamRegistry.createStream(restconfURI, new DeviceNotificationSource(mountPointService, path),
"All YANG notifications occuring on mount point /"
- + new ApiPathCanonizer(operPath.databind()).dataToApiPath(path).toString())
- .transform(stream -> new OperationsPostResult(operPath, ImmutableNodes.newContainerBuilder()
+ + new ApiPathCanonizer(input.path().databind()).dataToApiPath(path).toString())
+ .transform(stream -> ImmutableNodes.newContainerBuilder()
.withNodeIdentifier(new NodeIdentifier(SubscribeDeviceNotificationOutput.QNAME))
.withChild(ImmutableNodes.leafNode(DEVICE_NOTIFICATION_STREAM_NAME_NODEID, stream.name()))
- .build()));
+ .build());
}
}
import org.opendaylight.mdsal.dom.api.DOMDataBroker.DataTreeChangeExtension;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.spi.ApiPathCanonizer;
import org.opendaylight.restconf.server.spi.DatabindProvider;
import org.opendaylight.restconf.server.spi.OperationInput;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
* </pre>
*/
@Override
- public RestconfFuture<OperationsPostResult> invoke(final URI restconfURI, final OperationInput input) {
+ public RestconfFuture<ContainerNode> invoke(final URI restconfURI, final OperationInput input) {
final var body = input.input();
final var datastoreName = leaf(body, DATASTORE_NODEID, String.class);
final var datastore = datastoreName != null ? LogicalDatastoreType.valueOf(datastoreName)
new RestconfDocumentedException("missing path", ErrorType.APPLICATION, ErrorTag.MISSING_ELEMENT));
}
- final var operPath = input.path();
-
return streamRegistry.createStream(restconfURI,
new DataTreeChangeSource(databindProvider, changeService, datastore, path),
"Events occuring in " + datastore + " datastore under /"
- + new ApiPathCanonizer(operPath.databind()).dataToApiPath(path).toString())
- .transform(stream -> new OperationsPostResult(operPath, ImmutableNodes.newContainerBuilder()
+ + new ApiPathCanonizer(input.path().databind()).dataToApiPath(path).toString())
+ .transform(stream -> ImmutableNodes.newContainerBuilder()
.withNodeIdentifier(OUTPUT_NODEID)
.withChild(ImmutableNodes.leafNode(STREAM_NAME_NODEID, stream.name()))
- .build()));
+ .build());
}
}
import org.opendaylight.mdsal.dom.api.DOMNotificationService;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.spi.DatabindProvider;
import org.opendaylight.restconf.server.spi.OperationInput;
import org.opendaylight.restconf.server.spi.RestconfStream;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
}
@Override
- public RestconfFuture<OperationsPostResult> invoke(final URI restconfURI, final OperationInput input) {
+ public RestconfFuture<ContainerNode> invoke(final URI restconfURI, final OperationInput input) {
final var body = input.input();
final var qnames = ((LeafSetNode<String>) body.getChildByArg(NOTIFICATIONS)).body().stream()
.map(LeafSetEntryNode::body)
.sorted()
.collect(ImmutableSet.toImmutableSet());
- final var operPath = input.path();
- final var modelContext = operPath.databind().modelContext();
+ final var modelContext = input.path().databind().modelContext();
final var description = new StringBuilder("YANG notifications matching any of {");
var haveFirst = false;
for (var qname : qnames) {
return streamRegistry.createStream(restconfURI,
new NotificationSource(databindProvider, notificationService, qnames), description.toString())
- .transform(stream -> new OperationsPostResult(operPath, ImmutableNodes.newContainerBuilder()
+ .transform(stream -> ImmutableNodes.newContainerBuilder()
.withNodeIdentifier(SAL_REMOTE_OUTPUT_NODEID)
.withChild(ImmutableNodes.leafNode(STREAM_NAME_NODEID, stream.name()))
- .build()));
+ .build());
}
}
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
* @param input RPC input
* @return Future RPC output
*/
- public abstract RestconfFuture<OperationsPostResult> invoke(URI restconfURI, OperationInput input);
+ public abstract RestconfFuture<ContainerNode> invoke(URI restconfURI, OperationInput input);
@Override
public final String toString() {
static final void assertJson(final String expectedJson, final OperationOutputBody payload) {
final var baos = new ByteArrayOutputStream();
try {
- payload.writeJSON(baos);
+ payload.formatToJSON(baos);
} catch (IOException e) {
throw new AssertionError(e);
}
static final void assertXml(final String expectedXml, final OperationOutputBody payload) {
final var baos = new ByteArrayOutputStream();
try {
- payload.writeXML(baos);
+ payload.formatToXML(baos);
} catch (IOException e) {
throw new AssertionError(e);
}
static final List<RestconfError> assertErrors(final Consumer<AsyncResponse> invocation) {
final var ar = mock(AsyncResponse.class);
- final var captor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
- doReturn(true).when(ar).resume(captor.capture());
+ doReturn(true).when(ar).resume(any(RestconfDocumentedException.class));
+
invocation.accept(ar);
- verify(ar).resume(any(RestconfDocumentedException.class));
+
+ final var captor = ArgumentCaptor.forClass(RestconfDocumentedException.class);
+ verify(ar).resume(captor.capture());
return captor.getValue().getErrors();
}
static final Response assertResponse(final int expectedStatus, final Consumer<AsyncResponse> invocation) {
final var ar = mock(AsyncResponse.class);
- final var captor = ArgumentCaptor.forClass(Response.class);
- doReturn(true).when(ar).resume(captor.capture());
+ doReturn(true).when(ar).resume(any(Response.class));
+
invocation.accept(ar);
- verify(ar).resume(any(Response.class));
+
+ final var captor = ArgumentCaptor.forClass(Response.class);
+ verify(ar).resume(captor.capture());
final var response = captor.getValue();
assertEquals(expectedStatus, response.getStatus());
return response;
import com.google.common.util.concurrent.Futures;
import java.util.List;
import java.util.Optional;
+import javax.ws.rs.core.MultivaluedHashMap;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
return MODEL_CONTEXT;
}
+ @BeforeEach
+ void setupUriInfo() {
+ doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
+ }
+
@Test
void testInvokeRpcWithNonEmptyOutput() {
final var result = mock(ContainerNode.class);
doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit();
final var output = assertInstanceOf(ContainerNode.class, rpc.invoke(RESTCONF_URI, createInput("path", TOASTER))
- .getOrThrow().output());
+ .getOrThrow());
assertEquals(new NodeIdentifier(CreateDataChangeEventSubscriptionOutput.QNAME), output.name());
assertEquals(1, output.size());