diff --git a/components/abstractions/spotBugsExcludeFilter.xml b/components/abstractions/spotBugsExcludeFilter.xml index 8c33623ae..cf787f100 100644 --- a/components/abstractions/spotBugsExcludeFilter.xml +++ b/components/abstractions/spotBugsExcludeFilter.xml @@ -80,4 +80,10 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu + + + + + + diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/ClientException.java b/components/abstractions/src/main/java/com/microsoft/kiota/ClientException.java new file mode 100644 index 000000000..2be6368bd --- /dev/null +++ b/components/abstractions/src/main/java/com/microsoft/kiota/ClientException.java @@ -0,0 +1,28 @@ +package com.microsoft.kiota; + +import jakarta.annotation.Nonnull; + +/** An error in client-side request or response processing, rather than an API error response. */ +public class ClientException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** {@inheritDoc} */ + public ClientException() { + super(); + } + + /** {@inheritDoc} */ + public ClientException(@Nonnull final String message) { + super(message); + } + + /** {@inheritDoc} */ + public ClientException(@Nonnull final String message, @Nonnull final Throwable cause) { + super(message, cause); + } + + /** {@inheritDoc} */ + public ClientException(@Nonnull final Throwable cause) { + super(cause); + } +} diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/serialization/ParseNodeFactoryRegistry.java b/components/abstractions/src/main/java/com/microsoft/kiota/serialization/ParseNodeFactoryRegistry.java index 61e06c997..84f1b3d71 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/serialization/ParseNodeFactoryRegistry.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/serialization/ParseNodeFactoryRegistry.java @@ -1,5 +1,7 @@ package com.microsoft.kiota.serialization; +import com.microsoft.kiota.ClientException; + import jakarta.annotation.Nonnull; import java.io.InputStream; @@ -32,6 +34,9 @@ public ParseNodeFactoryRegistry() { private static final Pattern contentTypeVendorCleanupPattern = Pattern.compile("[^/]+\\+", Pattern.CASE_INSENSITIVE); + /** {@inheritDoc} + * @throws ClientException when no factory is registered for the content type + */ @Override @Nonnull public ParseNode getParseNode( @Nonnull final String contentType, @Nonnull final InputStream rawResponse) { @@ -53,7 +58,7 @@ public ParseNodeFactoryRegistry() { .get(cleanedContentType) .getParseNode(cleanedContentType, rawResponse); } - throw new RuntimeException( + throw new ClientException( "Content type " + cleanedContentType + " does not have a factory to be parsed"); } } diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/serialization/SerializationWriterFactoryRegistry.java b/components/abstractions/src/main/java/com/microsoft/kiota/serialization/SerializationWriterFactoryRegistry.java index 9fcb2cb23..af7cb3a97 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/serialization/SerializationWriterFactoryRegistry.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/serialization/SerializationWriterFactoryRegistry.java @@ -1,5 +1,6 @@ package com.microsoft.kiota.serialization; +import com.microsoft.kiota.ClientException; import com.microsoft.kiota.store.BackingStoreSerializationWriterProxyFactory; import jakarta.annotation.Nonnull; @@ -43,7 +44,7 @@ public SerializationWriterFactoryRegistry() { * @param contentType * @param serializeOnlyChangedValues control backing store functionality * @return the serialization writer - * @throws RuntimeException when no factory is found for content type + * @throws ClientException when no factory is found for content type */ @Nonnull public SerializationWriter getSerializationWriter( @Nonnull final String contentType, final boolean serializeOnlyChangedValues) { @@ -59,7 +60,7 @@ public SerializationWriterFactoryRegistry() { getSerializationWriterFactory( getCleanedVendorSpecificContentType(cleanedContentType)); if (factory == null) { - throw new RuntimeException( + throw new ClientException( "Content type " + contentType + " does not have a factory to be serialized"); diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/serialization/FactoryRegistryExceptionTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/serialization/FactoryRegistryExceptionTest.java new file mode 100644 index 000000000..bd00b5ec4 --- /dev/null +++ b/components/abstractions/src/test/java/com/microsoft/kiota/serialization/FactoryRegistryExceptionTest.java @@ -0,0 +1,80 @@ +package com.microsoft.kiota.serialization; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.microsoft.kiota.ClientException; +import com.microsoft.kiota.Compatibility; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +class FactoryRegistryExceptionTest { + @Test + void unsupportedResponseContentTypeThrowsClientException() { + var registry = new ParseNodeFactoryRegistry(); + var response = + new ByteArrayInputStream( + "Unavailable".getBytes(StandardCharsets.UTF_8)); + var error = + assertThrows( + ClientException.class, + () -> registry.getParseNode("text/html; charset=utf-8", response)); + assertEquals( + "Content type text/html does not have a factory to be parsed", error.getMessage()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void unsupportedRequestContentTypeThrowsClientException(boolean changedValuesOnly) { + var registry = new SerializationWriterFactoryRegistry(); + var error = + assertThrows( + ClientException.class, + () -> registry.getSerializationWriter("text/html", changedValuesOnly)); + assertEquals( + "Content type text/html does not have a factory to be serialized", + error.getMessage()); + } + + @Test + void invalidArgumentsKeepTheirValidationExceptions() { + var parser = new ParseNodeFactoryRegistry(); + var writer = new SerializationWriterFactoryRegistry(); + assertThrows( + NullPointerException.class, + () -> parser.getParseNode(null, new ByteArrayInputStream(new byte[0]))); + assertThrows( + NullPointerException.class, + () -> parser.getParseNode("", new ByteArrayInputStream(new byte[0]))); + assertThrows(NullPointerException.class, () -> parser.getParseNode("text/html", null)); + assertThrows(NullPointerException.class, () -> writer.getSerializationWriter(null)); + assertThrows(NullPointerException.class, () -> writer.getSerializationWriter("")); + } + + @Test + void registeredVendorContentTypesStillWork() throws Exception { + var parser = new ParseNodeFactoryRegistry(); + parser.contentTypeAssociatedFactories.put("application/json", new JsonParseNodeFactory()); + var response = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8)); + assertEquals( + 42, + parser.getParseNode("application/vnd.example+json; charset=utf-8", response) + .getIntegerValue()); + var writers = new SerializationWriterFactoryRegistry(); + writers.contentTypeAssociatedFactories.put( + "application/json", new JsonSerializationWriterFactory()); + try (var writer = writers.getSerializationWriter("application/vnd.example+json")) { + writer.writeIntegerValue(null, 42); + assertEquals( + "42", + new String( + Compatibility.readAllBytes(writer.getSerializedContent()), + StandardCharsets.UTF_8)); + } + } +}