Skip to content

Commit d24eb36

Browse files
authored
Merge pull request #1150 from piotrooo/feature/introduce-multipartbody-filename-directive
Introduce MultipartBody filename directive
2 parents a7a6569 + 97da359 commit d24eb36

File tree

5 files changed

+95
-16
lines changed

5 files changed

+95
-16
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.1.4] - 2024-04-04
11+
1012
### Added
1113

14+
- Introduces a `filename` directive in the `MultipartBody`.
15+
1216
### Changed
1317

1418
- Replaces `@Nullable` annotations to `@Nonnull` in the `BaseRequestConfiguration`.

components/abstractions/spotBugsExcludeFilter.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,8 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu
5858
<Bug pattern="NP_LOAD_OF_KNOWN_NULL_VALUE" />
5959
<Class name="com.microsoft.kiota.store.InMemoryBackingStore" />
6060
</Match>
61+
<Match>
62+
<Bug pattern="SIC_INNER_SHOULD_BE_STATIC" />
63+
<Class name="com.microsoft.kiota.MultipartBody$Part" />
64+
</Match>
6165
</FindBugsFilter>

components/abstractions/src/main/java/com/microsoft/kiota/MultipartBody.java

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,35 @@ public MultipartBody() {
4848
*/
4949
public <T> void addOrReplacePart(
5050
@Nonnull final String name, @Nonnull final String contentType, @Nonnull final T value) {
51+
addOrReplacePart(name, contentType, value, null);
52+
}
53+
54+
/**
55+
* Adds or replaces a part in the multipart body.
56+
*
57+
* @param <T> the type of the part to add or replace.
58+
* @param name the name of the part to add or replace.
59+
* @param contentType the content type of the part to add or replace.
60+
* @param value the value of the part to add or replace.
61+
* @param filename the value of the filename directive.
62+
*/
63+
public <T> void addOrReplacePart(
64+
@Nonnull final String name,
65+
@Nonnull final String contentType,
66+
@Nonnull final T value,
67+
@Nullable String filename) {
5168
Objects.requireNonNull(value);
5269
if (Compatibility.isBlank(contentType))
5370
throw new IllegalArgumentException("contentType cannot be blank or empty");
5471
if (Compatibility.isBlank(name))
5572
throw new IllegalArgumentException("name cannot be blank or empty");
5673

5774
final String normalizedName = normalizePartName(name);
58-
originalNames.put(normalizedName, name);
59-
parts.put(normalizedName, new AbstractMap.SimpleEntry<>(contentType, value));
75+
Part part = new Part(name, value, contentType, filename);
76+
parts.put(normalizedName, part);
6077
}
6178

62-
private final Map<String, Map.Entry<String, Object>> parts = new HashMap<>();
63-
private final Map<String, String> originalNames = new HashMap<>();
79+
private final Map<String, Part> parts = new HashMap<>();
6480

6581
private String normalizePartName(@Nonnull final String original) {
6682
return original.toLowerCase(Locale.ROOT);
@@ -75,7 +91,7 @@ private String normalizePartName(@Nonnull final String original) {
7591
if (Compatibility.isBlank(partName))
7692
throw new IllegalArgumentException("partName cannot be blank or empty");
7793
final String normalizedName = normalizePartName(partName);
78-
final Map.Entry<String, Object> candidate = parts.get(normalizedName);
94+
final Part candidate = parts.get(normalizedName);
7995
if (candidate == null) return null;
8096
return candidate.getValue();
8197
}
@@ -90,10 +106,7 @@ public boolean removePart(@Nonnull final String partName) {
90106
throw new IllegalArgumentException("partName cannot be blank or empty");
91107
final String normalizedName = normalizePartName(partName);
92108
final Object candidate = parts.remove(normalizedName);
93-
if (candidate == null) return false;
94-
95-
originalNames.remove(normalizedName);
96-
return true;
109+
return candidate != null;
97110
}
98111

99112
/** {@inheritDoc} */
@@ -111,18 +124,23 @@ public void serialize(@Nonnull final SerializationWriter writer) {
111124
if (parts.isEmpty()) throw new IllegalStateException("multipart body cannot be empty");
112125
final SerializationWriterFactory serializationFactory = ra.getSerializationWriterFactory();
113126
boolean isFirst = true;
114-
for (final Map.Entry<String, Map.Entry<String, Object>> partEntry : parts.entrySet()) {
127+
for (final Map.Entry<String, Part> partEntry : parts.entrySet()) {
115128
try {
129+
Part part = partEntry.getValue();
116130
if (isFirst) isFirst = false;
117131
else writer.writeStringValue("", "");
118132
writer.writeStringValue("", "--" + getBoundary());
119-
final String partContentType = partEntry.getValue().getKey();
133+
final String partContentType = part.getContentType();
120134
writer.writeStringValue("Content-Type", partContentType);
121-
writer.writeStringValue(
122-
"Content-Disposition",
123-
"form-data; name=\"" + originalNames.get(partEntry.getKey()) + "\"");
135+
136+
String contentDisposition = "form-data; name=\"" + part.getName() + "\"";
137+
if (part.getFilename() != null && !part.getFilename().trim().isEmpty()) {
138+
contentDisposition += "; filename=\"" + part.getFilename() + "\"";
139+
}
140+
writer.writeStringValue("Content-Disposition", contentDisposition);
141+
124142
writer.writeStringValue("", "");
125-
final Object objectValue = partEntry.getValue().getValue();
143+
final Object objectValue = part.getValue();
126144
if (objectValue instanceof Parsable) {
127145
try (final SerializationWriter partWriter =
128146
serializationFactory.getSerializationWriter(partContentType)) {
@@ -151,4 +169,34 @@ public void serialize(@Nonnull final SerializationWriter writer) {
151169
writer.writeStringValue("", "");
152170
writer.writeStringValue("", "--" + boundary + "--");
153171
}
172+
173+
private class Part {
174+
private final String name;
175+
private final Object value;
176+
private final String contentType;
177+
private final String filename;
178+
179+
Part(String name, Object value, String contentType, String filename) {
180+
this.name = name;
181+
this.value = value;
182+
this.contentType = contentType;
183+
this.filename = filename;
184+
}
185+
186+
public String getName() {
187+
return name;
188+
}
189+
190+
public Object getValue() {
191+
return value;
192+
}
193+
194+
public String getContentType() {
195+
return contentType;
196+
}
197+
198+
public String getFilename() {
199+
return filename;
200+
}
201+
}
154202
}

components/abstractions/src/test/java/com/microsoft/kiota/MultiPartBodyTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.junit.jupiter.api.Assertions.assertThrows;
66
import static org.junit.jupiter.api.Assertions.assertTrue;
77
import static org.mockito.Mockito.mock;
8+
import static org.mockito.Mockito.verify;
89

910
import com.microsoft.kiota.serialization.SerializationWriter;
1011

@@ -67,5 +68,27 @@ void removesPart() {
6768
final Object result = multipartBody.getPartValue("foo");
6869
assertNull(result);
6970
}
71+
72+
@Test
73+
void notAddFilename() {
74+
final MultipartBody multipartBody = new MultipartBody();
75+
final SerializationWriter writer = mock(SerializationWriter.class);
76+
multipartBody.requestAdapter = mock(RequestAdapter.class);
77+
multipartBody.addOrReplacePart("foo", "bar", "baz");
78+
multipartBody.serialize(writer);
79+
verify(writer).writeStringValue("Content-Disposition", "form-data; name=\"foo\"");
80+
}
81+
82+
@Test
83+
void addFilename() {
84+
final MultipartBody multipartBody = new MultipartBody();
85+
final SerializationWriter writer = mock(SerializationWriter.class);
86+
multipartBody.requestAdapter = mock(RequestAdapter.class);
87+
multipartBody.addOrReplacePart("foo", "bar", "baz", "image.png");
88+
multipartBody.serialize(writer);
89+
verify(writer)
90+
.writeStringValue(
91+
"Content-Disposition", "form-data; name=\"foo\"; filename=\"image.png\"");
92+
}
7093
// serialize method is being tested in the serialization library
7194
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ org.gradle.caching=true
2626
mavenGroupId = com.microsoft.kiota
2727
mavenMajorVersion = 1
2828
mavenMinorVersion = 1
29-
mavenPatchVersion = 3
29+
mavenPatchVersion = 4
3030
mavenArtifactSuffix =
3131

3232
#These values are used to run functional tests

0 commit comments

Comments
 (0)