From d1104393487ee2edec75789a0ccd911d3e85c609 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sat, 6 Apr 2024 02:45:21 +0200 Subject: [PATCH] Introduce YangPatchStatusBody Add a dedicated FormattableBody for YANG Patch status, eliminating the need for dedicated MessageBodyWriters. This has the nice result of unifying tests as well. JIRA: NETCONF-773 Change-Id: I06cb44fa4ea88b1e6505e5d3911ba3167503afa2 Signed-off-by: Robert Varga --- .../restconf/nb/jaxrs/JaxRsRestconf.java | 3 +- .../restconf/nb/rfc8040/JaxRsNorthbound.java | 6 +- .../AbstractPatchStatusBodyWriter.java | 37 ---- .../providers/JsonPatchStatusBodyWriter.java | 90 -------- .../providers/XmlPatchStatusBodyWriter.java | 122 ----------- .../server/spi/YangPatchStatusBody.java | 204 ++++++++++++++++++ .../nb/jaxrs/RestconfDataPatchTest.java | 58 ++--- .../JsonPatchStatusBodyWriterTest.java | 76 ------- .../spi/YangPatchStatusBodyTest.java} | 50 +++-- 9 files changed, 258 insertions(+), 388 deletions(-) delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractPatchStatusBodyWriter.java delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriter.java delete mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriter.java create mode 100644 restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java delete mode 100644 restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriterTest.java rename restconf/restconf-nb/src/test/java/org/opendaylight/restconf/{nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriterTest.java => server/spi/YangPatchStatusBodyTest.java} (63%) diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java index 4477c9811e..f691160568 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java @@ -75,6 +75,7 @@ import org.opendaylight.restconf.server.api.XmlDataPostBody; import org.opendaylight.restconf.server.api.XmlOperationInputBody; import org.opendaylight.restconf.server.api.XmlPatchBody; import org.opendaylight.restconf.server.api.XmlResourceBody; +import org.opendaylight.restconf.server.spi.YangPatchStatusBody; import org.opendaylight.yangtools.yang.common.Empty; import org.opendaylight.yangtools.yang.common.YangConstants; import org.slf4j.Logger; @@ -391,7 +392,7 @@ public final class JaxRsRestconf implements ParamConverterProvider { @Override Response transform(final DataYangPatchResult result) { final var status = result.status(); - final var builder = Response.status(statusOf(status)).entity(status); + final var builder = Response.status(statusOf(status)).entity(new YangPatchStatusBody(status)); fillConfigurationMetadata(builder, result); return builder.build(); } diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java index 36feabf539..41615f1010 100644 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/JaxRsNorthbound.java @@ -24,9 +24,7 @@ import org.opendaylight.restconf.nb.jaxrs.JaxRsWebHostMetadata; 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; -import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlPatchStatusBodyWriter; import org.opendaylight.restconf.nb.rfc8040.jersey.providers.errors.RestconfDocumentedExceptionMapper; import org.opendaylight.restconf.nb.rfc8040.streams.RestconfStreamServletFactory; import org.opendaylight.restconf.server.api.RestconfServer; @@ -62,9 +60,7 @@ public final class JaxRsNorthbound implements AutoCloseable { new Application() { @Override public Set> getClasses() { - return Set.of( - JsonNormalizedNodeBodyWriter.class, XmlNormalizedNodeBodyWriter.class, - JsonPatchStatusBodyWriter.class, XmlPatchStatusBodyWriter.class); + return Set.of(JsonNormalizedNodeBodyWriter.class, XmlNormalizedNodeBodyWriter.class); } @Override diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractPatchStatusBodyWriter.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractPatchStatusBodyWriter.java deleted file mode 100644 index bb2ceda3f9..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractPatchStatusBodyWriter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.nb.rfc8040.jersey.providers; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.io.OutputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyWriter; -import org.eclipse.jdt.annotation.NonNull; -import org.opendaylight.restconf.server.api.PatchStatusContext; - -abstract class AbstractPatchStatusBodyWriter implements MessageBodyWriter { - @Override - public final boolean isWriteable(final Class type, final Type genericType, final Annotation[] annotations, - final MediaType mediaType) { - return type.equals(PatchStatusContext.class); - } - - @Override - public final void writeTo(final PatchStatusContext body, final Class type, final Type genericType, - final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap httpHeaders, - final OutputStream entityStream) throws IOException { - writeTo(requireNonNull(body), requireNonNull(entityStream)); - } - - abstract void writeTo(@NonNull PatchStatusContext body, @NonNull OutputStream out) throws IOException; -} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriter.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriter.java deleted file mode 100644 index 8e800237a1..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. 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.nb.rfc8040.jersey.providers; - -import com.google.gson.stream.JsonWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; -import javax.ws.rs.Produces; -import javax.ws.rs.ext.Provider; -import org.opendaylight.restconf.api.MediaTypes; -import org.opendaylight.restconf.api.query.PrettyPrintParam; -import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.server.api.DatabindContext; -import org.opendaylight.restconf.server.api.PatchStatusContext; -import org.opendaylight.restconf.server.spi.FormattableBodySupport; - -@Provider -@Produces(MediaTypes.APPLICATION_YANG_DATA_JSON) -public class JsonPatchStatusBodyWriter extends AbstractPatchStatusBodyWriter { - @Override - void writeTo(final PatchStatusContext body, final OutputStream out) throws IOException { - final var jsonWriter = FormattableBodySupport.createJsonWriter(out, () -> PrettyPrintParam.FALSE); - jsonWriter.beginObject().name("ietf-yang-patch:yang-patch-status") - .beginObject().name("patch-id").value(body.patchId()); - - if (body.ok()) { - reportSuccess(jsonWriter); - } else { - final var globalErrors = body.globalErrors(); - if (globalErrors != null) { - reportErrors(body.databind(), globalErrors, jsonWriter); - } else { - jsonWriter.name("edit-status").beginObject() - .name("edit").beginArray(); - for (var editStatus : body.editCollection()) { - jsonWriter.beginObject().name("edit-id").value(editStatus.getEditId()); - - final var editErrors = editStatus.getEditErrors(); - if (editErrors != null) { - reportErrors(body.databind(), editErrors, jsonWriter); - } else if (editStatus.isOk()) { - reportSuccess(jsonWriter); - } - jsonWriter.endObject(); - } - jsonWriter.endArray().endObject(); - } - } - jsonWriter.endObject().endObject().flush(); - } - - private static void reportSuccess(final JsonWriter jsonWriter) throws IOException { - jsonWriter.name("ok").beginArray().nullValue().endArray(); - } - - private static void reportErrors(final DatabindContext databind, final List errors, - final JsonWriter jsonWriter) throws IOException { - jsonWriter.name("errors").beginObject().name("error").beginArray(); - - for (var restconfError : errors) { - jsonWriter.beginObject() - .name("error-type").value(restconfError.getErrorType().elementBody()) - .name("error-tag").value(restconfError.getErrorTag().elementBody()); - - final var errorPath = restconfError.getErrorPath(); - if (errorPath != null) { - jsonWriter.name("error-path"); - databind.jsonCodecs().instanceIdentifierCodec().writeValue(jsonWriter, errorPath); - } - final var errorMessage = restconfError.getErrorMessage(); - if (errorMessage != null) { - jsonWriter.name("error-message").value(errorMessage); - } - final var errorInfo = restconfError.getErrorInfo(); - if (errorInfo != null) { - jsonWriter.name("error-info").value(errorInfo); - } - - jsonWriter.endObject(); - } - - jsonWriter.endArray().endObject(); - } -} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriter.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriter.java deleted file mode 100644 index 533519b745..0000000000 --- a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriter.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2015 Cisco Systems, Inc. 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.nb.rfc8040.jersey.providers; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.List; -import javax.ws.rs.Produces; -import javax.ws.rs.ext.Provider; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import org.opendaylight.restconf.api.MediaTypes; -import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.server.api.DatabindContext; -import org.opendaylight.restconf.server.api.PatchStatusContext; -import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.status.YangPatchStatus; - -@Provider -@Produces(MediaTypes.APPLICATION_YANG_DATA_XML) -public class XmlPatchStatusBodyWriter extends AbstractPatchStatusBodyWriter { - private static final String XML_NAMESPACE = YangPatchStatus.QNAME.getNamespace().toString(); - private static final XMLOutputFactory XML_FACTORY; - - static { - XML_FACTORY = XMLOutputFactory.newFactory(); - XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); - } - - @Override - void writeTo(final PatchStatusContext body, final OutputStream out) throws IOException { - try { - final var xmlWriter = XML_FACTORY.createXMLStreamWriter(out, StandardCharsets.UTF_8.name()); - writeDocument(xmlWriter, body); - } catch (final XMLStreamException e) { - throw new IOException("Failed to write body", e); - } - } - - private static void writeDocument(final XMLStreamWriter writer, final PatchStatusContext body) - throws XMLStreamException { - writer.writeStartElement("", "yang-patch-status", XML_NAMESPACE); - writer.writeStartElement("patch-id"); - writer.writeCharacters(body.patchId()); - writer.writeEndElement(); - - if (body.ok()) { - writer.writeEmptyElement("ok"); - } else { - final var globalErrors = body.globalErrors(); - if (globalErrors != null) { - reportErrors(body.databind(), globalErrors, writer); - } else { - writer.writeStartElement("edit-status"); - for (var patchStatusEntity : body.editCollection()) { - writer.writeStartElement("edit"); - writer.writeStartElement("edit-id"); - writer.writeCharacters(patchStatusEntity.getEditId()); - writer.writeEndElement(); - - final var editErrors = patchStatusEntity.getEditErrors(); - if (editErrors != null) { - reportErrors(body.databind(), editErrors, writer); - } else if (patchStatusEntity.isOk()) { - writer.writeEmptyElement("ok"); - } - writer.writeEndElement(); - } - writer.writeEndElement(); - } - } - writer.writeEndElement(); - writer.flush(); - } - - private static void reportErrors(final DatabindContext databind, final List errors, - final XMLStreamWriter writer) throws XMLStreamException { - writer.writeStartElement("errors"); - - for (var restconfError : errors) { - writer.writeStartElement("error-type"); - writer.writeCharacters(restconfError.getErrorType().elementBody()); - writer.writeEndElement(); - - writer.writeStartElement("error-tag"); - writer.writeCharacters(restconfError.getErrorTag().elementBody()); - writer.writeEndElement(); - - // optional node - final var errorPath = restconfError.getErrorPath(); - if (errorPath != null) { - writer.writeStartElement("error-path"); - databind.xmlCodecs().instanceIdentifierCodec().writeValue(writer, errorPath); - writer.writeEndElement(); - } - - // optional node - final var errorMessage = restconfError.getErrorMessage(); - if (errorMessage != null) { - writer.writeStartElement("error-message"); - writer.writeCharacters(errorMessage); - writer.writeEndElement(); - } - - // optional node - final var errorInfo = restconfError.getErrorInfo(); - if (errorInfo != null) { - writer.writeStartElement("error-info"); - writer.writeCharacters(errorInfo); - writer.writeEndElement(); - } - } - - writer.writeEndElement(); - } -} diff --git a/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java new file mode 100644 index 0000000000..b2378da7b9 --- /dev/null +++ b/restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/spi/YangPatchStatusBody.java @@ -0,0 +1,204 @@ +/* + * 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.spi; + +import static java.util.Objects.requireNonNull; + +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.opendaylight.restconf.api.FormatParameters; +import org.opendaylight.restconf.api.FormattableBody; +import org.opendaylight.restconf.api.query.PrettyPrintParam; +import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.restconf.server.api.PatchStatusContext; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.status.YangPatchStatus; + +/** + * Result of a {@code PATCH} request as defined in + * RFC8072, section 2.3. + */ +public final class YangPatchStatusBody extends FormattableBody { + private static final String XML_NAMESPACE = YangPatchStatus.QNAME.getNamespace().toString(); + // FIXME: remove this factory + private static final XMLOutputFactory XML_FACTORY; + + static { + final var f = XMLOutputFactory.newFactory(); + f.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + XML_FACTORY = f; + } + + private final PatchStatusContext status; + + public YangPatchStatusBody(final FormatParameters format, final PatchStatusContext status) { + super(format); + this.status = requireNonNull(status); + } + + @Deprecated + public YangPatchStatusBody(final PatchStatusContext status) { + this(() -> PrettyPrintParam.FALSE, status); + } + + @Override + protected void formatToJSON(final OutputStream out, final FormatParameters format) throws IOException { + try (var writer = FormattableBodySupport.createJsonWriter(out, () -> PrettyPrintParam.FALSE)) { + writer.beginObject().name("ietf-yang-patch:yang-patch-status") + .beginObject().name("patch-id").value(status.patchId()); + + if (status.ok()) { + writeOk(writer); + } else { + final var globalErrors = status.globalErrors(); + if (globalErrors != null) { + writeErrors(globalErrors, writer); + } else { + writer.name("edit-status").beginObject() + .name("edit").beginArray(); + for (var editStatus : status.editCollection()) { + writer.beginObject().name("edit-id").value(editStatus.getEditId()); + + final var editErrors = editStatus.getEditErrors(); + if (editErrors != null) { + writeErrors(editErrors, writer); + } else if (editStatus.isOk()) { + writeOk(writer); + } + writer.endObject(); + } + writer.endArray().endObject(); + } + } + writer.endObject().endObject(); + } + } + + @Override + protected void formatToXML(final OutputStream out, final FormatParameters format) throws IOException { + try { + final var writer = XML_FACTORY.createXMLStreamWriter(out, StandardCharsets.UTF_8.name()); + formatToXML(writer); + } catch (XMLStreamException e) { + throw new IOException("Failed to write body", e); + } + } + + private void formatToXML(final XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement("", "yang-patch-status", XML_NAMESPACE); + writer.writeStartElement("patch-id"); + writer.writeCharacters(status.patchId()); + writer.writeEndElement(); + + if (status.ok()) { + writer.writeEmptyElement("ok"); + } else { + final var globalErrors = status.globalErrors(); + if (globalErrors != null) { + reportErrors(globalErrors, writer); + } else { + writer.writeStartElement("edit-status"); + for (var patchStatusEntity : status.editCollection()) { + writer.writeStartElement("edit"); + writer.writeStartElement("edit-id"); + writer.writeCharacters(patchStatusEntity.getEditId()); + writer.writeEndElement(); + + final var editErrors = patchStatusEntity.getEditErrors(); + if (editErrors != null) { + reportErrors(editErrors, writer); + } else if (patchStatusEntity.isOk()) { + writer.writeEmptyElement("ok"); + } + writer.writeEndElement(); + } + writer.writeEndElement(); + } + } + writer.writeEndElement(); + writer.flush(); + } + + private static void writeOk(final JsonWriter writer) throws IOException { + writer.name("ok").beginArray().nullValue().endArray(); + } + + private void writeErrors(final List errors, final JsonWriter writer) throws IOException { + writer.name("errors").beginObject().name("error").beginArray(); + + for (var restconfError : errors) { + writer.beginObject() + .name("error-type").value(restconfError.getErrorType().elementBody()) + .name("error-tag").value(restconfError.getErrorTag().elementBody()); + + final var errorPath = restconfError.getErrorPath(); + if (errorPath != null) { + writer.name("error-path"); + status.databind().jsonCodecs().instanceIdentifierCodec().writeValue(writer, errorPath); + } + final var errorMessage = restconfError.getErrorMessage(); + if (errorMessage != null) { + writer.name("error-message").value(errorMessage); + } + final var errorInfo = restconfError.getErrorInfo(); + if (errorInfo != null) { + writer.name("error-info").value(errorInfo); + } + + writer.endObject(); + } + + writer.endArray().endObject(); + } + + private void reportErrors(final List errors, final XMLStreamWriter writer) + throws XMLStreamException { + writer.writeStartElement("errors"); + + for (var restconfError : errors) { + writer.writeStartElement("error-type"); + writer.writeCharacters(restconfError.getErrorType().elementBody()); + writer.writeEndElement(); + + writer.writeStartElement("error-tag"); + writer.writeCharacters(restconfError.getErrorTag().elementBody()); + writer.writeEndElement(); + + // optional node + final var errorPath = restconfError.getErrorPath(); + if (errorPath != null) { + writer.writeStartElement("error-path"); + status.databind().xmlCodecs().instanceIdentifierCodec().writeValue(writer, errorPath); + writer.writeEndElement(); + } + + // optional node + final var errorMessage = restconfError.getErrorMessage(); + if (errorMessage != null) { + writer.writeStartElement("error-message"); + writer.writeCharacters(errorMessage); + writer.writeEndElement(); + } + + // optional node + final var errorInfo = restconfError.getErrorInfo(); + if (errorInfo != null) { + writer.writeStartElement("error-info"); + writer.writeCharacters(errorInfo); + writer.writeEndElement(); + } + } + + writer.writeEndElement(); + } +} diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java index 4d9ade7227..2f9070b8a2 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfDataPatchTest.java @@ -7,10 +7,6 @@ */ package org.opendaylight.restconf.nb.jaxrs; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFalseFluentFuture; @@ -24,9 +20,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction; -import org.opendaylight.restconf.server.api.PatchStatusContext; -import org.opendaylight.yangtools.yang.common.ErrorTag; -import org.opendaylight.yangtools.yang.common.ErrorType; +import org.opendaylight.restconf.server.spi.YangPatchStatusBody; @ExtendWith(MockitoExtension.class) class RestconfDataPatchTest extends AbstractRestconfTest { @@ -44,7 +38,8 @@ class RestconfDataPatchTest extends AbstractRestconfTest { doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID); doReturn(immediateTrueFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID); doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit(); - final var status = assertEntity(PatchStatusContext.class, 200, + + final var body = assertEntity(YangPatchStatusBody.class, 200, ar -> restconf.dataYangJsonPATCH(stringInputStream(""" { "ietf-yang-patch:yang-patch" : { @@ -82,12 +77,9 @@ class RestconfDataPatchTest extends AbstractRestconfTest { ] } }"""), ar)); - assertTrue(status.ok()); - final var edits = status.editCollection(); - assertEquals(3, edits.size()); - assertTrue(edits.get(0).isOk()); - assertTrue(edits.get(1).isOk()); - assertTrue(edits.get(2).isOk()); + + assertFormat(""" + {"ietf-yang-patch:yang-patch-status":{"patch-id":"test patch id","ok":[null]}}""", body::formatToJSON); } @Test @@ -97,7 +89,7 @@ class RestconfDataPatchTest extends AbstractRestconfTest { doReturn(immediateFalseFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID); doReturn(true).when(tx).cancel(); - final var status = assertEntity(PatchStatusContext.class, 409, ar -> restconf.dataYangJsonPATCH( + final var body = assertEntity(YangPatchStatusBody.class, 409, ar -> restconf.dataYangJsonPATCH( stringInputStream(""" { "ietf-yang-patch:yang-patch" : { @@ -128,20 +120,14 @@ class RestconfDataPatchTest extends AbstractRestconfTest { ] } }"""), ar)); - assertFalse(status.ok()); - final var edits = status.editCollection(); - assertEquals(3, edits.size()); - assertTrue(edits.get(0).isOk()); - assertTrue(edits.get(1).isOk()); - final var edit = edits.get(2); - assertFalse(edit.isOk()); - final var errors = edit.getEditErrors(); - assertEquals(1, errors.size()); - final var error = errors.get(0); - assertEquals("Data does not exist", error.getErrorMessage()); - assertEquals(ErrorType.PROTOCOL, error.getErrorType()); - assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag()); - assertEquals(GAP_IID, error.getErrorPath()); + + assertFormat(""" + {"ietf-yang-patch:yang-patch-status":{"patch-id":"test patch id","edit-status":{"edit":[\ + {"edit-id":"create data","ok":[null]},\ + {"edit-id":"remove data","ok":[null]},\ + {"edit-id":"delete data","errors":{"error":[{"error-type":"protocol","error-tag":"data-missing",\ + "error-path":"/example-jukebox:jukebox/player/gap","error-message":"Data does not exist"}]}}]}}}""", + body::formatToJSON); } @Test @@ -151,7 +137,7 @@ class RestconfDataPatchTest extends AbstractRestconfTest { doReturn(immediateTrueFluentFuture()).when(tx).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID); doReturn(CommitInfo.emptyFluentFuture()).when(tx).commit(); - final var status = assertEntity(PatchStatusContext.class, 200, + final var body = assertEntity(YangPatchStatusBody.class, 200, ar -> restconf.dataYangXmlPATCH(stringInputStream(""" test patch id @@ -185,12 +171,10 @@ class RestconfDataPatchTest extends AbstractRestconfTest { /example-jukebox:jukebox/player/gap """), ar)); - assertTrue(status.ok()); - assertNull(status.globalErrors()); - final var edits = status.editCollection(); - assertEquals(3, edits.size()); - assertTrue(edits.get(0).isOk()); - assertTrue(edits.get(1).isOk()); - assertTrue(edits.get(2).isOk()); + + assertFormat(""" + \ + test patch id\ + """, body::formatToXML); } } diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriterTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriterTest.java deleted file mode 100644 index d6b0abe152..0000000000 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/JsonPatchStatusBodyWriterTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2023 PANTHEON.tech s.r.o. 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.nb.rfc8040.jersey.providers; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import javax.ws.rs.core.MediaType; -import org.junit.Test; -import org.opendaylight.restconf.common.errors.RestconfError; -import org.opendaylight.restconf.server.api.DatabindContext; -import org.opendaylight.restconf.server.api.PatchStatusContext; -import org.opendaylight.restconf.server.api.PatchStatusEntity; -import org.opendaylight.yangtools.yang.common.ErrorTag; -import org.opendaylight.yangtools.yang.common.ErrorType; -import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; - -public class JsonPatchStatusBodyWriterTest { - private final RestconfError error = new RestconfError(ErrorType.PROTOCOL, new ErrorTag("data-exists"), - "Data already exists"); - private final PatchStatusEntity statusEntity = new PatchStatusEntity("patch1", true, null); - private final PatchStatusEntity statusEntityError = new PatchStatusEntity("patch1", false, List.of(error)); - private final DatabindContext databind = DatabindContext.ofModel(mock(EffectiveModelContext.class)); - private final JsonPatchStatusBodyWriter writer = new JsonPatchStatusBodyWriter(); - - /** - * Test if per-operation status is omitted if global error is present. - */ - @Test - public void testOutputWithGlobalError() throws IOException { - final var outputStream = new ByteArrayOutputStream(); - final var patchStatusContext = new PatchStatusContext(databind,"patch", List.of(statusEntity), - false, List.of(error)); - writer.writeTo(patchStatusContext, null, null, null, MediaType.APPLICATION_XML_TYPE, null, outputStream); - - assertEquals(""" - {"ietf-yang-patch:yang-patch-status":{\ - "patch-id":"patch",\ - "errors":{"error":[{\ - "error-type":"protocol",\ - "error-tag":"data-exists",\ - "error-message":"Data already exists"\ - }]}}}""", outputStream.toString(StandardCharsets.UTF_8)); - } - - /** - * Test if per-operation status is present if there is no global error present. - */ - @Test - public void testOutputWithoutGlobalError() throws IOException { - final var outputStream = new ByteArrayOutputStream(); - final var patchStatusContext = new PatchStatusContext(databind, "patch", List.of(statusEntityError), - false, null); - writer.writeTo(patchStatusContext, null, null, null, MediaType.APPLICATION_XML_TYPE, null, outputStream); - - assertEquals(""" - {"ietf-yang-patch:yang-patch-status":{\ - "patch-id":"patch",\ - "edit-status":{"edit":[{\ - "edit-id":"patch1",\ - "errors":{"error":[{\ - "error-type":"protocol",\ - "error-tag":"data-exists",\ - "error-message":"Data already exists"\ - }]}}]}}}""", outputStream.toString(StandardCharsets.UTF_8)); - } -} diff --git a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriterTest.java b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java similarity index 63% rename from restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriterTest.java rename to restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java index 9b81f71bce..e50839f6ed 100644 --- a/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlPatchStatusBodyWriterTest.java +++ b/restconf/restconf-nb/src/test/java/org/opendaylight/restconf/server/spi/YangPatchStatusBodyTest.java @@ -5,18 +5,15 @@ * 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.nb.rfc8040.jersey.providers; +package org.opendaylight.restconf.server.spi; -import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.List; -import javax.ws.rs.core.MediaType; import org.junit.Test; import org.opendaylight.restconf.common.errors.RestconfError; +import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest; import org.opendaylight.restconf.server.api.DatabindContext; import org.opendaylight.restconf.server.api.PatchStatusContext; import org.opendaylight.restconf.server.api.PatchStatusEntity; @@ -24,33 +21,37 @@ import org.opendaylight.yangtools.yang.common.ErrorTag; import org.opendaylight.yangtools.yang.common.ErrorType; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; -public class XmlPatchStatusBodyWriterTest { +public class YangPatchStatusBodyTest extends AbstractJukeboxTest { private final RestconfError error = new RestconfError(ErrorType.PROTOCOL, new ErrorTag("data-exists"), "Data already exists"); private final PatchStatusEntity statusEntity = new PatchStatusEntity("patch1", true, null); private final PatchStatusEntity statusEntityError = new PatchStatusEntity("patch1", false, List.of(error)); private final DatabindContext databind = DatabindContext.ofModel(mock(EffectiveModelContext.class)); - private final XmlPatchStatusBodyWriter writer = new XmlPatchStatusBodyWriter(); /** * Test if per-operation status is omitted if global error is present. */ @Test public void testOutputWithGlobalError() throws IOException { - final var outputStream = new ByteArrayOutputStream(); - final var patchStatusContext = new PatchStatusContext(databind, "patch", List.of(statusEntity), - false, List.of(error)); - writer.writeTo(patchStatusContext, null, null, null, MediaType.APPLICATION_XML_TYPE, null, outputStream); + final var body = new YangPatchStatusBody(new PatchStatusContext(databind, "patch", List.of(statusEntity), + false, List.of(error))); - assertEquals(""" + assertFormat(""" + {"ietf-yang-patch:yang-patch-status":{\ + "patch-id":"patch",\ + "errors":{"error":[{\ + "error-type":"protocol",\ + "error-tag":"data-exists",\ + "error-message":"Data already exists"\ + }]}}}""", body::formatToJSON); + assertFormat(""" \ patch\ \ protocol\ data-exists\ Data already exists\ - """, outputStream.toString(StandardCharsets.UTF_8)); - + """, body::formatToXML); } /** @@ -58,11 +59,20 @@ public class XmlPatchStatusBodyWriterTest { */ @Test public void testOutputWithoutGlobalError() throws IOException { - final var outputStream = new ByteArrayOutputStream(); - final var patchStatusContext = new PatchStatusContext(databind,"patch", List.of(statusEntityError), - false, null); - writer.writeTo(patchStatusContext, null, null, null, MediaType.APPLICATION_XML_TYPE, null, outputStream); - assertEquals(""" + final var body = new YangPatchStatusBody(new PatchStatusContext(databind,"patch", List.of(statusEntityError), + false, null)); + + assertFormat(""" + {"ietf-yang-patch:yang-patch-status":{\ + "patch-id":"patch",\ + "edit-status":{"edit":[{\ + "edit-id":"patch1",\ + "errors":{"error":[{\ + "error-type":"protocol",\ + "error-tag":"data-exists",\ + "error-message":"Data already exists"\ + }]}}]}}}""", body::formatToJSON); + assertFormat(""" \ patch\ \ @@ -72,6 +82,6 @@ public class XmlPatchStatusBodyWriterTest { data-exists\ Data already exists\ \ - """, outputStream.toString(StandardCharsets.UTF_8)); + """, body::formatToXML); } } -- 2.36.6