diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 420409ac3..23229986f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,6 +20,11 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 +- package-ecosystem: gradle + directory: "/components/serialization/multipart" + schedule: + interval: daily + open-pull-requests-limit: 10 - package-ecosystem: gradle directory: "/components/authentication/azure" schedule: @@ -50,6 +55,11 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 +- package-ecosystem: gradle + directory: "/components/serialization/multipart/android" + schedule: + interval: daily + open-pull-requests-limit: 10 - package-ecosystem: gradle directory: "/components/authentication/azure/android" schedule: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acdc91366..4a444d6c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,6 +46,7 @@ jobs: Copy-Item secring.gpg components/serialization/form/ -Verbose Copy-Item secring.gpg components/serialization/text/ -Verbose Copy-Item secring.gpg components/serialization/json/ -Verbose + Copy-Item secring.gpg components/serialization/multipart/ -Verbose Copy-Item secring.gpg components/http/okHttp/ -Verbose shell: pwsh working-directory: ./ @@ -90,6 +91,7 @@ jobs: Copy-Item secring.gpg components/serialization/form/ -Verbose Copy-Item secring.gpg components/serialization/text/ -Verbose Copy-Item secring.gpg components/serialization/json/ -Verbose + Copy-Item secring.gpg components/serialization/multipart/ -Verbose Copy-Item secring.gpg components/http/okHttp/ -Verbose shell: pwsh working-directory: ./ @@ -108,6 +110,9 @@ jobs: - name: Publish Release serialization text run: ./gradlew --no-daemon :components:serialization:text:$PUBLISH_TASK -PmavenCentralSnapshotArtifactSuffix="" working-directory: ./ + - name: Publish Release serialization multipart + run: ./gradlew --no-daemon :components:serialization:multipart:$PUBLISH_TASK -PmavenCentralSnapshotArtifactSuffix="" + working-directory: ./ - name: Publish Release authentication azure run: ./gradlew --no-daemon :components:authentication:azure:$PUBLISH_TASK -PmavenCentralSnapshotArtifactSuffix="" working-directory: ./ diff --git a/CHANGELOG.md b/CHANGELOG.md index be1672993..d2071ecd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +## [0.5.0] - 2023-07-26 + +### Added + +- Added support for multipart form data request bodies. + ## [0.4.7] - 2023-07-21 ### Added diff --git a/README.md b/README.md index 2215ce983..1cc77ead1 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,13 @@ The Kiota Java Libraries for Java are: - - [abstractions] Defining the basic constructs Kiota projects need once an SDK has been generated from an OpenAPI definition - - [authentication/azure] Implementing Azure authentication mechanisms - - [http/okHttp] Implementing a default OkHttp client - - [serialization/form] Implementing default serialization for forms - - [serialization/json] Implementing default serialization for json - - [serialization/text] Implementing default serialization for text +- [abstractions] Defining the basic constructs Kiota projects need once an SDK has been generated from an OpenAPI definition +- [authentication/azure] Implementing Azure authentication mechanisms +- [http/okHttp] Implementing a default OkHttp client +- [serialization/form] Implementing default serialization for forms +- [serialization/json] Implementing default serialization for json +- [serialization/text] Implementing default serialization for text +- [serialization/multipart] Implementing default serialization for multipart Read more about Kiota [here](https://github.com/microsoft/kiota/blob/main/README.md). @@ -20,12 +21,13 @@ Read more about Kiota [here](https://github.com/microsoft/kiota/blob/main/README In `build.gradle` in the `dependencies` section: ```Groovy -implementation 'com.microsoft.kiota:microsoft-kiota-abstractions:0.4.0' -implementation 'com.microsoft.kiota:microsoft-kiota-authentication-azure:0.4.0' -implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:0.4.0' -implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:0.4.0' -implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:0.4.0' -implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:0.4.0' +implementation 'com.microsoft.kiota:microsoft-kiota-abstractions:0.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-authentication-azure:0.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-http-okHttp:0.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-json:0.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-text:0.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-form:0.5.0' +implementation 'com.microsoft.kiota:microsoft-kiota-serialization-multipart:0.5.0' ``` ### With Maven: @@ -36,32 +38,37 @@ In `pom.xml` in the `dependencies` section: com.microsoft.kiota microsoft-kiota-abstractions - 0.4.0 + 0.5.0 com.microsoft.kiota microsoft-kiota-authentication-azure - 0.4.0 + 0.5.0 com.microsoft.kiota microsoft-kiota-http-okHttp - 0.4.0 + 0.5.0 com.microsoft.kiota microsoft-kiota-serialization-json - 0.4.0 + 0.5.0 com.microsoft.kiota microsoft-kiota-serialization-text - 0.4.0 + 0.5.0 com.microsoft.kiota microsoft-kiota-serialization-form - 0.4.0 + 0.5.0 + + + com.microsoft.kiota + microsoft-kiota-serialization-multipart + 0.5.0 ``` diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/MultipartBody.java b/components/abstractions/src/main/java/com/microsoft/kiota/MultipartBody.java new file mode 100644 index 000000000..984c8eaae --- /dev/null +++ b/components/abstractions/src/main/java/com/microsoft/kiota/MultipartBody.java @@ -0,0 +1,168 @@ +package com.microsoft.kiota; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Consumer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.base.Strings; +import com.microsoft.kiota.serialization.Parsable; +import com.microsoft.kiota.serialization.ParseNode; +import com.microsoft.kiota.serialization.SerializationWriter; +import com.microsoft.kiota.serialization.SerializationWriterFactory; + +/** + * Represents a multipart body for a request or a response. + */ +public class MultipartBody implements Parsable { + @Nonnull + private final String boundary = UUID.randomUUID().toString().replace("-", ""); + /** @return the boundary string for the multipart body. */ + @Nonnull + public String getBoundary() { + return boundary; + } + /** + * The request adapter to use for the multipart body serialization. + */ + @Nullable + public RequestAdapter requestAdapter; + /** + * Adds or replaces a part in the multipart body. + * @param the type of the part to add or replace. + * @param name the name of the part to add or replace. + * @param contentType the content type of the part to add or replace. + * @param value the value of the part to add or replace. + */ + public void addOrReplacePart(@Nonnull final String name, @Nonnull final String contentType, @Nonnull final T value) { + Objects.requireNonNull(value); + if (Strings.isNullOrEmpty(contentType) || contentType.isBlank()) + throw new IllegalArgumentException("contentType cannot be blank or empty"); + if (Strings.isNullOrEmpty(name) || name.isBlank()) + throw new IllegalArgumentException("name cannot be blank or empty"); + + final String normalizedName = normalizePartName(name); + originalNames.put(normalizedName, name); + parts.put(normalizedName, Map.entry(contentType, value)); + } + private final Map> parts = new HashMap<>(); + private final Map originalNames = new HashMap<>(); + private String normalizePartName(@Nonnull final String original) + { + return original.toLowerCase(Locale.ROOT); + } + /** + * Gets the content type of the part with the specified name. + * @param partName the name of the part to get. + * @return the content type of the part with the specified name. + */ + @Nullable + public Object getPartValue(@Nonnull final String partName) + { + if (Strings.isNullOrEmpty(partName) || partName.isBlank()) + throw new IllegalArgumentException("partName cannot be blank or empty"); + final String normalizedName = normalizePartName(partName); + final Map.Entry candidate = parts.get(normalizedName); + if(candidate == null) + return null; + return candidate.getValue(); + } + /** + * Gets the content type of the part with the specified name. + * @param partName the name of the part to get. + * @return the content type of the part with the specified name. + */ + public boolean removePart(@Nonnull final String partName) + { + if (Strings.isNullOrEmpty(partName) || partName.isBlank()) + throw new IllegalArgumentException("partName cannot be blank or empty"); + final String normalizedName = normalizePartName(partName); + final Object candidate = parts.remove(normalizedName); + if(candidate == null) + return false; + + originalNames.remove(normalizedName); + return true; + } + + /** {@inheritDoc} */ + @Override + @Nonnull + public Map> getFieldDeserializers() { + throw new UnsupportedOperationException("Unimplemented method 'getFieldDeserializers'"); + } + + /** {@inheritDoc} */ + @Override + public void serialize(@Nonnull final SerializationWriter writer) { + Objects.requireNonNull(writer); + final RequestAdapter ra = requestAdapter; + if (ra == null) + throw new IllegalStateException("requestAdapter cannot be null"); + if (parts.isEmpty()) + throw new IllegalStateException("multipart body cannot be empty"); + final SerializationWriterFactory serializationFactory = ra.getSerializationWriterFactory(); + boolean isFirst = true; + for (final Map.Entry> partEntry : parts.entrySet()) { + try { + if (isFirst) + isFirst = false; + else + writer.writeStringValue("", ""); + writer.writeStringValue("", "--" + getBoundary()); + final String partContentType = partEntry.getValue().getKey(); + writer.writeStringValue("Content-Type", partContentType); + writer.writeStringValue("Content-Disposition", "form-data; name=\"" + originalNames.get(partEntry.getKey()) + "\""); + writer.writeStringValue("", ""); + final Object objectValue = partEntry.getValue().getValue(); + if (objectValue instanceof Parsable) { + try (final SerializationWriter partWriter = serializationFactory.getSerializationWriter(partContentType)) { + partWriter.writeObjectValue("", ((Parsable)objectValue)); + try (final InputStream partContent = partWriter.getSerializedContent()) { + if (partContent.markSupported()) + partContent.reset(); + writer.writeByteArrayValue("", readAllBytes(partContent)); + } + } + } else if (objectValue instanceof String) { + writer.writeStringValue("", (String)objectValue); + } else if (objectValue instanceof InputStream) { + final InputStream inputStream = (InputStream)objectValue; + if (inputStream.markSupported()) + inputStream.reset(); + writer.writeByteArrayValue("", readAllBytes(inputStream)); + } else if (objectValue instanceof byte[]) { + writer.writeByteArrayValue("", (byte[])objectValue); + } else { + throw new IllegalStateException("Unsupported part type" + objectValue.getClass().getName()); + } + } catch (final IOException ex) { + throw new RuntimeException(ex); + } + } + writer.writeStringValue("", ""); + writer.writeStringValue("", "--" + boundary + "--"); + } + @Nonnull + private byte[] readAllBytes(@Nonnull final InputStream inputStream) throws IOException { + // InputStream.readAllBytes() is only available to Android API level 33+ + final int bufLen = 1024; + byte[] buf = new byte[bufLen]; + int readLen; + try(final ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + while ((readLen = inputStream.read(buf, 0, bufLen)) != -1) + outputStream.write(buf, 0, readLen); + return outputStream.toByteArray(); + } + } +} diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/RequestInformation.java b/components/abstractions/src/main/java/com/microsoft/kiota/RequestInformation.java index da31f1d53..730af0c85 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/RequestInformation.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/RequestInformation.java @@ -240,7 +240,13 @@ public void setContentFromParsable(@Nonnull final RequestAd final Span span = GlobalOpenTelemetry.getTracer(OBSERVABILITY_TRACER_NAME).spanBuilder(SPAN_NAME).startSpan(); try (final Scope scope = span.makeCurrent()) { try(final SerializationWriter writer = getSerializationWriter(requestAdapter, contentType, value)) { - headers.add(CONTENT_TYPE_HEADER, contentType); + String effectiveContentType = contentType; + if (value instanceof MultipartBody) { + final MultipartBody multipartBody = (MultipartBody)value; + effectiveContentType += "; boundary=" + multipartBody.getBoundary(); + multipartBody.requestAdapter = requestAdapter; + } + headers.add(CONTENT_TYPE_HEADER, effectiveContentType); setRequestType(value, span); writer.writeObjectValue(null, value); this.content = writer.getSerializedContent(); diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/MultiPartBodyTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/MultiPartBodyTest.java new file mode 100644 index 000000000..5679cb2b9 --- /dev/null +++ b/components/abstractions/src/test/java/com/microsoft/kiota/MultiPartBodyTest.java @@ -0,0 +1,60 @@ +package com.microsoft.kiota; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.Test; + +import com.microsoft.kiota.serialization.SerializationWriter; + +class MultiPartBodyTest { + @Test + void defensive() { + final MultipartBody multipartBody = new MultipartBody(); + assertThrows(IllegalArgumentException.class, () -> multipartBody.addOrReplacePart(null, "foo", "bar")); + assertThrows(IllegalArgumentException.class, () -> multipartBody.addOrReplacePart("foo", null, "bar")); + assertThrows(NullPointerException.class, () -> multipartBody.addOrReplacePart("foo", "bar", null)); + assertThrows(IllegalArgumentException.class, () -> multipartBody.getPartValue(null)); + assertThrows(IllegalArgumentException.class, () -> multipartBody.removePart(null)); + assertThrows(NullPointerException.class, () -> multipartBody.serialize(null)); + assertThrows(UnsupportedOperationException.class, () -> multipartBody.getFieldDeserializers()); + } + @Test + void requiresRequestAdapter(){ + final MultipartBody multipartBody = new MultipartBody(); + final SerializationWriter writer = mock(SerializationWriter.class); + assertThrows(IllegalStateException.class, () -> multipartBody.serialize(writer)); + } + @Test + void requiresPartsForSerialization() { + final MultipartBody multipartBody = new MultipartBody(); + final SerializationWriter writer = mock(SerializationWriter.class); + final RequestAdapter requestAdapter = mock(RequestAdapter.class); + multipartBody.requestAdapter = requestAdapter; + assertThrows(IllegalStateException.class, () -> multipartBody.serialize(writer)); + } + @Test + void addsPart() { + final MultipartBody multipartBody = new MultipartBody(); + final RequestAdapter requestAdapter = mock(RequestAdapter.class); + multipartBody.requestAdapter = requestAdapter; + multipartBody.addOrReplacePart("foo", "bar", "baz"); + final Object result = multipartBody.getPartValue("foo"); + assertNotNull(result); + assertTrue(result instanceof String); + } + @Test + void removesPart() { + final MultipartBody multipartBody = new MultipartBody(); + final RequestAdapter requestAdapter = mock(RequestAdapter.class); + multipartBody.requestAdapter = requestAdapter; + multipartBody.addOrReplacePart("foo", "bar", "baz"); + multipartBody.removePart("FOO"); + final Object result = multipartBody.getPartValue("foo"); + assertNull(result); + } + // serialize method is being tested in the serialization library +} diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/RequestInformationTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/RequestInformationTest.java index de963c85f..1112e5c6f 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/RequestInformationTest.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/RequestInformationTest.java @@ -18,9 +18,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class RequestInformationTest { +class RequestInformationTest { @Test - public void ThrowsInvalidOperationExceptionWhenBaseUrlNotSet() + void ThrowsInvalidOperationExceptionWhenBaseUrlNotSet() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); @@ -33,7 +33,7 @@ public void ThrowsInvalidOperationExceptionWhenBaseUrlNotSet() } @Test - public void BuildsUrlOnProvidedBaseUrl() + void BuildsUrlOnProvidedBaseUrl() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); @@ -49,7 +49,7 @@ public void BuildsUrlOnProvidedBaseUrl() } @Test - public void SetsPathParametersOfDateTimeOffsetType() + void SetsPathParametersOfDateTimeOffsetType() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); @@ -69,7 +69,7 @@ public void SetsPathParametersOfDateTimeOffsetType() } @Test - public void ExpandQueryParametersAfterPathParams() + void ExpandQueryParametersAfterPathParams() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); @@ -89,7 +89,7 @@ public void ExpandQueryParametersAfterPathParams() } @Test - public void DoesNotSetQueryParametersParametersIfEmptyString() + void DoesNotSetQueryParametersParametersIfEmptyString() { // Arrange as the request builders would @@ -111,7 +111,7 @@ public void DoesNotSetQueryParametersParametersIfEmptyString() } @Test - public void DoesNotSetQueryParametersParametersIfEmptyCollection() + void DoesNotSetQueryParametersParametersIfEmptyCollection() { // Arrange as the request builders would @@ -131,7 +131,7 @@ public void DoesNotSetQueryParametersParametersIfEmptyCollection() } @Test - public void SetsPathParametersOfBooleanType() + void SetsPathParametersOfBooleanType() { // Arrange as the request builders would @@ -148,7 +148,7 @@ public void SetsPathParametersOfBooleanType() assertTrue(uriResult.toString().contains("count=true")); } @Test - public void SetsParsableContent() { + void SetsParsableContent() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); requestInfo.httpMethod= HttpMethod.POST; @@ -164,7 +164,7 @@ public void SetsParsableContent() { verify(writerMock, never()).writeCollectionOfObjectValues(anyString(), any(ArrayList.class)); } @Test - public void SetsParsableContentCollection() { + void SetsParsableContentCollection() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); requestInfo.httpMethod= HttpMethod.POST; @@ -180,7 +180,7 @@ public void SetsParsableContentCollection() { verify(writerMock, times(1)).writeCollectionOfObjectValues(any(), any(Iterable.class)); } @Test - public void SetsScalarContentCollection() { + void SetsScalarContentCollection() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); requestInfo.httpMethod= HttpMethod.POST; @@ -196,7 +196,7 @@ public void SetsScalarContentCollection() { verify(writerMock, times(1)).writeCollectionOfPrimitiveValues(any(), any(Iterable.class)); } @Test - public void SetsScalarContent() { + void SetsScalarContent() { // Arrange as the request builders would final RequestInformation requestInfo = new RequestInformation(); requestInfo.httpMethod= HttpMethod.POST; @@ -211,6 +211,25 @@ public void SetsScalarContent() { verify(writerMock, times(1)).writeStringValue(any(), anyString()); verify(writerMock, never()).writeCollectionOfPrimitiveValues(any(), any(Iterable.class)); } + @Test + void SetsBoundaryOnMultipartBody() { + final RequestInformation requestInfo = new RequestInformation(); + requestInfo.httpMethod= HttpMethod.POST; + requestInfo.urlTemplate = "http://localhost/{URITemplate}/ParameterMapping?IsCaseSensitive={IsCaseSensitive}"; + final SerializationWriter writerMock = mock(SerializationWriter.class); + final SerializationWriterFactory factoryMock = mock(SerializationWriterFactory.class); + when(factoryMock.getSerializationWriter(anyString())).thenReturn(writerMock); + final RequestAdapter requestAdapterMock = mock(RequestAdapter.class); + when(requestAdapterMock.getSerializationWriterFactory()).thenReturn(factoryMock); + + final MultipartBody multipartBody = new MultipartBody(); + multipartBody.requestAdapter = requestAdapterMock; + + requestInfo.setContentFromParsable(requestAdapterMock, "multipart/form-data", multipartBody); + assertNotNull(multipartBody.getBoundary()); + assertFalse(multipartBody.getBoundary().isEmpty()); + assertEquals("multipart/form-data; boundary=" + multipartBody.getBoundary(), requestInfo.headers.get("Content-Type").toArray()[0]); + } } diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java index 3c85b0924..9f0163ea9 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java @@ -14,7 +14,7 @@ public UserAgentHandlerOption() { } @Nonnull private String productName = "kiota-java"; @Nonnull - private String productVersion = "0.2.0"; + private String productVersion = "0.5.0"; /** * Gets the product name to be used in the user agent header * @return the product name diff --git a/components/serialization/multipart/.gitignore b/components/serialization/multipart/.gitignore new file mode 100644 index 000000000..20a548b24 --- /dev/null +++ b/components/serialization/multipart/.gitignore @@ -0,0 +1,10 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + + +.settings +.project +.classpath \ No newline at end of file diff --git a/components/serialization/multipart/android/.gitignore b/components/serialization/multipart/android/.gitignore new file mode 100644 index 000000000..f06dfad69 --- /dev/null +++ b/components/serialization/multipart/android/.gitignore @@ -0,0 +1,2 @@ +.gradle +build \ No newline at end of file diff --git a/components/serialization/multipart/android/AndroidManifest.xml b/components/serialization/multipart/android/AndroidManifest.xml new file mode 100644 index 000000000..2932f8879 --- /dev/null +++ b/components/serialization/multipart/android/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/components/serialization/multipart/android/build.gradle b/components/serialization/multipart/android/build.gradle new file mode 100644 index 000000000..29f8f6f8c --- /dev/null +++ b/components/serialization/multipart/android/build.gradle @@ -0,0 +1,73 @@ +buildscript { + repositories { + google() + gradlePluginPortal() + maven { + url "https://plugins.gradle.org/m2/" + } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + } + + dependencies { + classpath "com.gradle:gradle-enterprise-gradle-plugin:3.14" + classpath "com.android.tools.build:gradle:8.1.0" + classpath "com.github.ben-manes:gradle-versions-plugin:0.47.0" + } +} + +repositories { + google() + gradlePluginPortal() +} + +apply plugin: "com.android.library" +apply plugin: "com.github.ben-manes.versions" + +android { + namespace "com.microsoft.kiota.serialization" + compileSdkVersion 34 + + defaultConfig { + versionCode 1 + versionName "1.0" + minSdkVersion 26 + targetSdkVersion 34 + } + + buildTypes { + release { + minifyEnabled false + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + textOutput "stdout" + checkAllWarnings true + warningsAsErrors true + disable "UnusedResources" // Unused will be removed on release + disable "IconExpectedSize" // Using the material icons provided from Google + disable "GoogleAppIndexingApiWarning" // We might want to index our app later + disable "InvalidPackage" // Butterknife, Okio and Realm + disable "ResourceType" // Annotation binding + disable "GradleDependency" + disable "NewerVersionAvailable" + disable "DuplicatePlatformClasses" // xpp3 added by azure-identity + } + sourceSets { + main { + java.srcDirs = ['../src/main/java'] + res.srcDirs = ['../src/main/java'] + manifest.srcFile 'AndroidManifest.xml' + } + androidTest { + setRoot '../src/test' + } + } +} + +apply from: "../gradle/dependencies.gradle" diff --git a/components/serialization/multipart/android/gradle.properties b/components/serialization/multipart/android/gradle.properties new file mode 100644 index 000000000..c4b9317c4 --- /dev/null +++ b/components/serialization/multipart/android/gradle.properties @@ -0,0 +1 @@ +mavenArtifactId = microsoft-kiota-serialization-multipart diff --git a/components/serialization/multipart/build.gradle b/components/serialization/multipart/build.gradle new file mode 100644 index 000000000..7eeb6a51a --- /dev/null +++ b/components/serialization/multipart/build.gradle @@ -0,0 +1,239 @@ +plugins { + // Apply the java-library plugin to add support for Java Library + id 'java-library' + id 'java' + id 'eclipse' + id 'maven-publish' + id 'signing' + id 'jacoco' + id 'com.github.spotbugs' version '5.0.14' + id "org.sonarqube" version "4.3.0.3225" +} + +java { + modularity.inferModulePath = true + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + finalizedBy jacocoTestReport // report is always generated after tests run +} + +jacocoTestReport { + dependsOn test // tests are required to run before generating the report +} + +jacoco { + toolVersion = "0.8.10" +} + +spotbugsMain { + excludeFilter = file("spotBugsExcludeFilter.xml") + reports { + html { + required + outputLocation = file("$buildDir/reports/spotbugs/main/spotbugs.html") + stylesheet = 'fancy-hist.xsl' + } + } +} + +spotbugsTest { + excludeFilter = file("spotBugsExcludeFilter.xml") + reports { + html { + required + outputLocation = file("$buildDir/reports/spotbugs/test/spotbugs.html") + stylesheet = 'fancy-hist.xsl' + } + } +} + +jacocoTestReport { + reports { + xml.required + } +} + +sourceSets { + main { + java { + exclude 'pom.xml' + } + } +} + +// In this section you declare where to find the dependencies of your project +repositories { + // You can declare any Maven/Ivy/file repository here. + mavenCentral() +} + +apply from: "gradle/dependencies.gradle" + +def pomConfig = { + licenses { + license([:]) { + name "MIT License" + url "http://opensource.org/licenses/MIT" + distribution "repo" + } + } +} + +sonarqube { + properties { + property "sonar.projectKey", "microsoft_kiota-java" + property "sonar.organization", "microsoft" + property "sonar.host.url", "https://sonarcloud.io" + } +} + +//Publishing tasks- +//Maven Central Snapshot: publishMavenPublicationToMavenRepository +//Maven Central Release: publishmavenPublicationToMaven2Repository + +tasks.jar { + manifest { + attributes('Automatic-Module-Name': project.property('mavenGroupId')) + } +} + +publishing { + + publications { + maven(MavenPublication) { + customizePom(pom) + groupId project.property('mavenGroupId') + artifactId project.property('mavenArtifactId') + version "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}${mavenCentralSnapshotArtifactSuffix}" + from components.java + pom.withXml { + def pomFile = file("${project.buildDir}/generated-pom.xml") + writeTo(pomFile) + } + } + } + repositories { + maven { + url = 'https://oss.sonatype.org/content/repositories/snapshots' + name = 'sonatypeSnapshot' + + credentials { + if (project.rootProject.file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + username = properties.getProperty('sonatypeUsername') + password = properties.getProperty('sonatypePassword') + } + } + } + + maven { + url = 'https://oss.sonatype.org/service/local/staging/deploy/maven2' + name = 'sonatype' + + credentials { + if (project.rootProject.file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + username = properties.getProperty('sonatypeUsername') + password = properties.getProperty('sonatypePassword') + } + } + } + } +} + +signing { + sign publishing.publications.maven +} +tasks.withType(Sign)*.enabled = mavenCentralPublishingEnabled.toBoolean() + +def fixAscNames = { name -> + if(name.contains('pom')) { + "${project.property('mavenArtifactId')}-${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}.pom.asc" + } else { + name.replace('microsoft-kiota-java-serialization-multipart', "${project.property('mavenArtifactId')}-${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}") + } +} + +compileJava { + options.compilerArgs << "-parameters" + sourceCompatibility = '1.8' + targetCompatibility = '1.8' +} + +def getVersionCode() { + return mavenMajorVersion.toInteger() * 10000 + mavenMinorVersion.toInteger() * 100 + mavenPatchVersion.toInteger() +} + +def getVersionName() { + return "${mavenMajorVersion}.${mavenMinorVersion}.${mavenPatchVersion}${mavenArtifactSuffix}" +} + +artifacts { + archives jar +} + +def customizePom(pom) { + pom.withXml { + def root = asNode() + + root.dependencies.removeAll { dep -> + dep.scope == "test" + } + + root.children().last() + { + resolveStrategy = Closure.DELEGATE_FIRST + + description 'Microsoft Kiota-Serialization for Multipart' + name 'Microsoft Kiota-Java Serialization-Multipart' + url 'https://github.com/microsoft/kiota-java' + organization { + name 'Microsoft' + url 'https://github.com/microsoft/kiota-java' + } + issueManagement { + system 'GitHub' + url 'https://github.com/microsoft/kiota-java/issues' + } + licenses { + license { + name "MIT License" + url "http://opensource.org/licenses/MIT" + distribution "repo" + } + } + scm { + url 'https://github.com/microsoft/kiota-java' + connection 'scm:git:git://github.com/microsoft/kiota-java.git' + developerConnection 'scm:git:ssh://git@github.com:microsoft/kiota-java.git' + } + developers { + developer { + name 'Microsoft' + } + } + } + } +} + +gradle.taskGraph.whenReady { taskGraph -> + if (project.rootProject.file('local.properties').exists()) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + tasks.withType(Sign)*.enabled = (properties.containsKey('enableSigning')) ? properties.getProperty('enableSigning').toBoolean() : false + allprojects { ext."signing.keyId" = properties.getProperty('signing.keyId') } + allprojects { ext."signing.secretKeyRingFile" = properties.getProperty('signing.secretKeyRingFile') } + allprojects { ext."signing.password" = properties.getProperty('signing.password') } + } +} + +model { + tasks.generatePomFileForMavenPublication { + destination = file("${project.buildDir}/generated-pom.xml") + } +} \ No newline at end of file diff --git a/components/serialization/multipart/gradle.properties b/components/serialization/multipart/gradle.properties new file mode 100644 index 000000000..c4b9317c4 --- /dev/null +++ b/components/serialization/multipart/gradle.properties @@ -0,0 +1 @@ +mavenArtifactId = microsoft-kiota-serialization-multipart diff --git a/components/serialization/multipart/gradle/dependencies.gradle b/components/serialization/multipart/gradle/dependencies.gradle new file mode 100644 index 000000000..6ef8cf374 --- /dev/null +++ b/components/serialization/multipart/gradle/dependencies.gradle @@ -0,0 +1,14 @@ +dependencies { + // Use JUnit Jupiter API for testing. + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' + testImplementation 'org.mockito:mockito-inline:5.2.0' + + // Use JUnit Jupiter Engine for testing. + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + + // This dependency is used internally, and not exposed to consumers on their own compile classpath. + implementation 'com.google.guava:guava:32.1.2-jre' + + api project(':components:abstractions') + testImplementation project(':components:serialization:json') +} diff --git a/components/serialization/multipart/spotBugsExcludeFilter.xml b/components/serialization/multipart/spotBugsExcludeFilter.xml new file mode 100644 index 000000000..d9952e56b --- /dev/null +++ b/components/serialization/multipart/spotBugsExcludeFilter.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/components/serialization/multipart/src/main/java/com/microsoft/kiota/serialization/MultipartSerializationWriter.java b/components/serialization/multipart/src/main/java/com/microsoft/kiota/serialization/MultipartSerializationWriter.java new file mode 100644 index 000000000..29e40e96f --- /dev/null +++ b/components/serialization/multipart/src/main/java/com/microsoft/kiota/serialization/MultipartSerializationWriter.java @@ -0,0 +1,174 @@ +package com.microsoft.kiota.serialization; + +import com.microsoft.kiota.PeriodAndDuration; +import com.microsoft.kiota.MultipartBody; + +import java.math.BigDecimal; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.Base64; +import java.util.EnumSet; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.function.BiConsumer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Serialization writer implementation for Multipart encoded payloads */ +public class MultipartSerializationWriter implements SerializationWriter { + private final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + private final OutputStreamWriter writer; + /** Instantiates a new MultipartSerializationWriter. */ + public MultipartSerializationWriter() { + try { + this.writer = new OutputStreamWriter(this.stream, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("could not create writer", e); + } + } + public void writeStringValue(@Nullable final String key, @Nullable final String value) { + try { + if (key != null && !key.isEmpty()) + writer.write(key); + if (value != null && !value.isEmpty()) { + if (key != null && !key.isEmpty()) + writer.write(": "); + writer.write(value); + } + writer.write("\r\n"); + writer.flush(); + } catch (IOException ex) { + throw new RuntimeException("could not serialize value", ex); + } + } + public void writeBooleanValue(@Nullable final String key, @Nullable final Boolean value) { + throw new UnsupportedOperationException(); + } + public void writeShortValue(@Nullable final String key, @Nullable final Short value) { + throw new UnsupportedOperationException(); + } + public void writeByteValue(@Nullable final String key, @Nullable final Byte value) { + throw new UnsupportedOperationException(); + } + public void writeBigDecimalValue(@Nullable final String key, @Nullable final BigDecimal value) { + throw new UnsupportedOperationException(); + } + public void writeIntegerValue(@Nullable final String key, @Nullable final Integer value) { + throw new UnsupportedOperationException(); + } + public void writeFloatValue(@Nullable final String key, @Nullable final Float value) { + throw new UnsupportedOperationException(); + } + public void writeDoubleValue(@Nullable final String key, @Nullable final Double value) { + throw new UnsupportedOperationException(); + } + public void writeLongValue(@Nullable final String key, @Nullable final Long value) { + throw new UnsupportedOperationException(); + } + public void writeUUIDValue(@Nullable final String key, @Nullable final UUID value) { + throw new UnsupportedOperationException(); + } + public void writeOffsetDateTimeValue(@Nullable final String key, @Nullable final OffsetDateTime value) { + throw new UnsupportedOperationException(); + } + public void writeLocalDateValue(@Nullable final String key, @Nullable final LocalDate value) { + throw new UnsupportedOperationException(); + } + public void writeLocalTimeValue(@Nullable final String key, @Nullable final LocalTime value) { + throw new UnsupportedOperationException(); + } + public void writePeriodAndDurationValue(@Nullable final String key, @Nullable final PeriodAndDuration value) { + throw new UnsupportedOperationException(); + } + public void writeCollectionOfPrimitiveValues(@Nullable final String key, @Nullable final Iterable values) { + throw new UnsupportedOperationException(); + } + public void writeCollectionOfObjectValues(@Nullable final String key, @Nullable final Iterable values) { + throw new UnsupportedOperationException(); + } + public > void writeCollectionOfEnumValues(@Nullable final String key, @Nullable final Iterable values) { + throw new UnsupportedOperationException(); + } + public void writeObjectValue(@Nullable final String key, @Nullable final T value, @Nonnull final Parsable ...additionalValuesToMerge) { + Objects.requireNonNull(additionalValuesToMerge); + if(value != null) { + if(onBeforeObjectSerialization != null) { + onBeforeObjectSerialization.accept(value); + } + if(value instanceof MultipartBody) { + if(onStartObjectSerialization != null) { + onStartObjectSerialization.accept(value, this); + } + value.serialize(this); + } else { + throw new RuntimeException("expected MultipartBody instance but got " + value.getClass().getName()); + } + if(onAfterObjectSerialization != null) { + onAfterObjectSerialization.accept(value); + } + } + } + public > void writeEnumSetValue(@Nullable final String key, @Nullable final EnumSet values) { + throw new UnsupportedOperationException(); + } + public > void writeEnumValue(@Nullable final String key, @Nullable final T value) { + throw new UnsupportedOperationException(); + } + public void writeNullValue(@Nullable final String key) { + throw new UnsupportedOperationException(); + } + @Nonnull + public InputStream getSerializedContent() { + return new ByteArrayInputStream(this.stream.toByteArray()); + } + public void close() throws IOException { + this.writer.close(); + this.stream.close(); + } + public void writeAdditionalData(@Nonnull final Map value) { + throw new UnsupportedOperationException(); + } + @Nullable + public Consumer getOnBeforeObjectSerialization() { + return this.onBeforeObjectSerialization; + } + @Nullable + public Consumer getOnAfterObjectSerialization() { + return this.onAfterObjectSerialization; + } + @Nullable + public BiConsumer getOnStartObjectSerialization() { + return this.onStartObjectSerialization; + } + private Consumer onBeforeObjectSerialization; + public void setOnBeforeObjectSerialization(@Nullable final Consumer value) { + this.onBeforeObjectSerialization = value; + } + private Consumer onAfterObjectSerialization; + public void setOnAfterObjectSerialization(@Nullable final Consumer value) { + this.onAfterObjectSerialization = value; + } + private BiConsumer onStartObjectSerialization; + public void setOnStartObjectSerialization(@Nullable final BiConsumer value) { + this.onStartObjectSerialization = value; + } + public void writeByteArrayValue(@Nullable final String key, @Nullable @Nonnull final byte[] value) { + if(value != null) + try { + this.stream.write(value); + } catch (IOException ex) { + throw new RuntimeException("could not serialize value", ex); + } + } +} diff --git a/components/serialization/multipart/src/main/java/com/microsoft/kiota/serialization/MultipartSerializationWriterFactory.java b/components/serialization/multipart/src/main/java/com/microsoft/kiota/serialization/MultipartSerializationWriterFactory.java new file mode 100644 index 000000000..6411d1fcc --- /dev/null +++ b/components/serialization/multipart/src/main/java/com/microsoft/kiota/serialization/MultipartSerializationWriterFactory.java @@ -0,0 +1,28 @@ +package com.microsoft.kiota.serialization; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +/** Creates instances of Multipart Serialization Writers */ +public class MultipartSerializationWriterFactory implements SerializationWriterFactory { + /** Instantiates a new factory */ + public MultipartSerializationWriterFactory() { + } + @Nonnull + public String getValidContentType() { + return validContentType; + } + private static final String validContentType = "multipart/form-data"; + @Override + @Nonnull + public SerializationWriter getSerializationWriter(@Nonnull final String contentType) { + Objects.requireNonNull(contentType, "parameter contentType cannot be null"); + if(contentType.isEmpty()) { + throw new NullPointerException("contentType cannot be empty"); + } else if (!contentType.equals(validContentType)) { + throw new IllegalArgumentException("expected a " + validContentType + " content type"); + } + return new MultipartSerializationWriter(); + } +} diff --git a/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/MultipartSerializationWriterTests.java b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/MultipartSerializationWriterTests.java new file mode 100644 index 000000000..88aa2eb7e --- /dev/null +++ b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/MultipartSerializationWriterTests.java @@ -0,0 +1,89 @@ +package com.microsoft.kiota.serialization; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; + +import com.microsoft.kiota.PeriodAndDuration; +import com.microsoft.kiota.MultipartBody; +import com.microsoft.kiota.RequestAdapter; +import com.microsoft.kiota.serialization.JsonSerializationWriterFactory; +import org.junit.jupiter.api.Test; + +import com.microsoft.kiota.serialization.mocks.TestEntity; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MultipartSerializationWriterTests { + @Test + public void throwsOnParsable() throws IOException, UnsupportedEncodingException { + final var testEntity = new TestEntity(); + testEntity.setId("48d31887-5fad-4d73-a9f5-3c356e68a038"); + testEntity.setWorkDuration(PeriodAndDuration.parse("P1M")); + testEntity.setStartWorkTime(LocalTime.of(8, 0, 0)); + testEntity.setBirthDay(LocalDate.of(2017, 9, 4)); + testEntity.setDeviceNames(Arrays.asList("device1","device2")); + testEntity.getAdditionalData().put("mobilePhone", null); + testEntity.getAdditionalData().put("jobTitle", "Author"); + testEntity.getAdditionalData().put("accountEnabled", false); + testEntity.getAdditionalData().put("createdDateTime", OffsetDateTime.MIN); + testEntity.getAdditionalData().put("otherPhones", Arrays.asList(Arrays.asList("device1","device2"))); + try (final var serializationWriter = new MultipartSerializationWriter()) { + assertThrows(RuntimeException.class, () -> serializationWriter.writeObjectValue(null, testEntity)); + } + } + private final byte[] byteForTest = new byte[] { 0x01, 0x02, 0x03 }; + @Test + public void writesBytArrayValue() throws IOException { + try (final var serializationWriter = new MultipartSerializationWriter()) { + serializationWriter.writeByteArrayValue("key", byteForTest); + try(final var result = serializationWriter.getSerializedContent()) { + try(final var reader = new BufferedReader(new InputStreamReader(result, "UTF-8"))) { + final String strResult = reader.lines().collect(Collectors.joining("\n")); + assertEquals("\u0001\u0002\u0003", strResult); + } + } + } + } + @Test + public void writesAStructuredObject() throws IOException { + final TestEntity testEntity = new TestEntity(); + testEntity.setId("48d31887-5fad-4d73-a9f5-3c356e68a038"); + testEntity.setWorkDuration(PeriodAndDuration.parse("P1M")); + testEntity.setStartWorkTime(LocalTime.of(8, 0, 0)); + testEntity.setBirthDay(LocalDate.of(2017, 9, 4)); + testEntity.setDeviceNames(Arrays.asList("device1","device2")); + testEntity.getAdditionalData().put("mobilePhone", null); + testEntity.getAdditionalData().put("jobTitle", "Author"); + testEntity.getAdditionalData().put("accountEnabled", false); + testEntity.getAdditionalData().put("createdDateTime", OffsetDateTime.MIN); + testEntity.getAdditionalData().put("otherPhones", Arrays.asList(Arrays.asList("device1","device2"))); + final RequestAdapter requestAdapter = mock(RequestAdapter.class); + when(requestAdapter.getSerializationWriterFactory()).thenReturn(new JsonSerializationWriterFactory()); + try (final var serializationWriter = new MultipartSerializationWriter()) { + final MultipartBody multipartBody = new MultipartBody(); + multipartBody.requestAdapter = requestAdapter; + multipartBody.addOrReplacePart("testEntity", "application/json", testEntity); + multipartBody.addOrReplacePart("image", "application/octet-stream", byteForTest); + serializationWriter.writeObjectValue(null, multipartBody); + + try(final var result = serializationWriter.getSerializedContent()) { + try(final var reader = new BufferedReader(new InputStreamReader(result, "UTF-8"))) { + final String strResult = reader.lines().collect(Collectors.joining("\r\n")); + assertEquals("--" + multipartBody.getBoundary() + "\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n"+new String(byteForTest, "UTF-8")+"\r\n--" + multipartBody.getBoundary() + "\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name=\"testEntity\"\r\n\r\n{\"id\":\"48d31887-5fad-4d73-a9f5-3c356e68a038\",\"birthDay\":\"2017-09-04\",\"workDuration\":\"P1M\",\"startWorkTime\":\"08:00:00\",\"deviceNames\":[\"device1\",\"device2\"],\"mobilePhone\":null,\"jobTitle\":\"Author\",\"createdDateTime\":\"-999999999-01-01T00:00:00+18:00\",\"otherPhones\":[[\"device1\",\"device2\"]],\"accountEnabled\":false}\r\n--" + multipartBody.getBoundary() + "--", strResult); + } + } + } + } +} diff --git a/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/SerializationWriterFactoryTests.java b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/SerializationWriterFactoryTests.java new file mode 100644 index 000000000..73e918f0f --- /dev/null +++ b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/SerializationWriterFactoryTests.java @@ -0,0 +1,24 @@ +package com.microsoft.kiota.serialization; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class SerializationWriterFactoryTests { + private static final MultipartSerializationWriterFactory _serializationWriterFactory = new MultipartSerializationWriterFactory(); + private static final String contentType = "multipart/form-data"; + @Test + public void getsWriterForMultipartContentType() { + final var serializationWriter = _serializationWriterFactory.getSerializationWriter(contentType); + assertNotNull(serializationWriter); + } + @Test + public void throwsArgumentOutOfRangeExceptionForInvalidContentType() { + assertThrows(IllegalArgumentException.class, () -> _serializationWriterFactory.getSerializationWriter("application/json")); + } + @Test + public void throwsArgumentNullExceptionForNoContentType() { + assertThrows(NullPointerException.class, () -> _serializationWriterFactory.getSerializationWriter("")); + } +} diff --git a/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/mocks/SecondTestEntity.java b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/mocks/SecondTestEntity.java new file mode 100644 index 000000000..71793aa62 --- /dev/null +++ b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/mocks/SecondTestEntity.java @@ -0,0 +1,70 @@ +package com.microsoft.kiota.serialization.mocks; + +import com.microsoft.kiota.serialization.ParseNode; +import com.microsoft.kiota.serialization.SerializationWriter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +import com.microsoft.kiota.serialization.Parsable; +import com.microsoft.kiota.serialization.AdditionalDataHolder; + +public class SecondTestEntity implements Parsable, AdditionalDataHolder { + private String _displayName; + private Integer _id; + private Long _failureRate; + + @Override + public Map> getFieldDeserializers() { + return new HashMap<>(){{ + put("displayName", (n) -> setDisplayName(n.getStringValue())); + put("id", (n) -> setId(n.getIntegerValue())); + put("failureRate", (n) -> setFailureRate(n.getLongValue())); + }}; + } + + @Override + public void serialize(SerializationWriter writer) { + Objects.requireNonNull(writer); + + writer.writeStringValue("displayName", getDisplayName()); + writer.writeIntegerValue("id", getId()); + writer.writeLongValue("failureRate", getFailureRate()); + writer.writeAdditionalData(getAdditionalData()); + } + private final Map _additionalData = new HashMap<>(); + @Override + public Map getAdditionalData() { + return _additionalData; + } + + public String getDisplayName() { + return _displayName; + } + + public void setDisplayName(String value) { + this._displayName = value; + } + + public Integer getId() { + return _id; + } + + public void setId(Integer value) { + this._id = value; + } + + public Long getFailureRate() { + return _failureRate; + } + + public void setFailureRate(Long value) { + this._failureRate = value; + } + @javax.annotation.Nonnull + public static SecondTestEntity createFromDiscriminatorValue(@javax.annotation.Nonnull final ParseNode parseNode) { + return new SecondTestEntity(); + } +} diff --git a/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/mocks/TestEntity.java b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/mocks/TestEntity.java new file mode 100644 index 000000000..e2eefbc07 --- /dev/null +++ b/components/serialization/multipart/src/test/java/com/microsoft/kiota/serialization/mocks/TestEntity.java @@ -0,0 +1,144 @@ +package com.microsoft.kiota.serialization.mocks; + +import com.microsoft.kiota.PeriodAndDuration; +import com.microsoft.kiota.serialization.ParseNode; +import com.microsoft.kiota.serialization.SerializationWriter; + +import java.util.*; +import java.util.function.Consumer; +import java.time.OffsetDateTime; +import java.time.LocalDate; +import java.time.LocalTime; + +import com.microsoft.kiota.serialization.Parsable; +import com.microsoft.kiota.serialization.AdditionalDataHolder; + +public class TestEntity implements Parsable, AdditionalDataHolder { + private String _id; + public String getId() { + return _id; + } + + public void setId(String _id) { + this._id = _id; + } + + private String _officeLocation; + public String getOfficeLocation() { + return _officeLocation; + } + + public void setOfficeLocation(String _officeLocation) { + this._officeLocation = _officeLocation; + } + + private LocalDate _birthDay; + public LocalDate getBirthDay() { + return _birthDay; + } + + public void setBirthDay(LocalDate value) { + this._birthDay = value; + } + + private List _deviceNames; + public List getDeviceNames() { + return _deviceNames; + } + + public void setDeviceNames(List value) { + this._deviceNames = new ArrayList(value); + } + private PeriodAndDuration _workDuration; + public PeriodAndDuration getWorkDuration() { + return _workDuration; + } + + public void setWorkDuration(PeriodAndDuration value) { + this._workDuration = value; + } + + private LocalTime _startWorkTime; + public LocalTime getStartWorkTime() { + return _startWorkTime; + } + + public void setStartWorkTime(LocalTime value) { + this._startWorkTime = value; + } + + private LocalTime _endWorkTime; + public LocalTime getEndWorkTime() { + return _endWorkTime; + } + + public void setEndWorkTime(LocalTime value) { + this._endWorkTime = value; + } + + //TODO enum + private OffsetDateTime _createdDateTime; + + public OffsetDateTime getCreatedDateTime() { + return _createdDateTime; + } + + public void setCreatedDateTime(OffsetDateTime value) { + this._createdDateTime = value; + } + + @Override + public Map> getFieldDeserializers() { + return new HashMap<>() {{ + put("id", (n) -> { + setId(n.getStringValue()); + }); + put("officeLocation", (n) -> { + setOfficeLocation(n.getStringValue()); + }); + put("birthDay", (n) -> { + setBirthDay(n.getLocalDateValue()); + }); + put("workDuration", (n) -> { + setWorkDuration(n.getPeriodAndDurationValue()); + }); + put("startWorkTime", (n) -> { + setStartWorkTime(n.getLocalTimeValue()); + }); + put("endWorkTime", (n) -> { + setEndWorkTime(n.getLocalTimeValue()); + }); + put("createdDateTime", (n) -> { + setCreatedDateTime(n.getOffsetDateTimeValue()); + }); + put("deviceNames", (n) -> { + setDeviceNames(n.getCollectionOfPrimitiveValues(String.class)); + }); + }}; + } + + @Override + public void serialize(SerializationWriter writer) { + Objects.requireNonNull(writer); + writer.writeStringValue("id", getId()); + writer.writeStringValue("officeLocation", getOfficeLocation()); + writer.writeLocalDateValue("birthDay", getBirthDay()); + writer.writePeriodAndDurationValue("workDuration", getWorkDuration()); + writer.writeLocalTimeValue("startWorkTime", getStartWorkTime()); + writer.writeLocalTimeValue("endWorkTime", getEndWorkTime()); + writer.writeOffsetDateTimeValue("createdDateTime", getCreatedDateTime()); + writer.writeCollectionOfPrimitiveValues("deviceNames", getDeviceNames()); + writer.writeAdditionalData(getAdditionalData()); + } + + private final Map _additionalData = new HashMap<>(); + + @Override + public Map getAdditionalData() { + return _additionalData; + } + @javax.annotation.Nonnull + public static TestEntity createFromDiscriminatorValue(@javax.annotation.Nonnull final ParseNode parseNode) { + return new TestEntity(); + } +} diff --git a/gradle.properties b/gradle.properties index 6a1346055..be6ba7aef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,8 +25,8 @@ org.gradle.caching=true mavenGroupId = com.microsoft.kiota mavenMajorVersion = 0 -mavenMinorVersion = 4 -mavenPatchVersion = 7 +mavenMinorVersion = 5 +mavenPatchVersion = 0 mavenArtifactSuffix = #These values are used to run functional tests diff --git a/settings.gradle b/settings.gradle index f9579ac0d..bff5bcbbc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,5 +10,6 @@ include ':components:abstractions' + suffix include ':components:serialization:form' + suffix include ':components:serialization:json' + suffix include ':components:serialization:text' + suffix +include ':components:serialization:multipart' + suffix include ':components:authentication:azure' + suffix include ':components:http:okHttp' + suffix