Skip to content

fix(multipart): omit nil header string values and support additionalValuesToMerge - #2220

Open
Hardik K (hardikkaurani) wants to merge 2 commits into
microsoft:mainfrom
hardikkaurani:fix/multipart-omit-nil-headers-and-additional-values
Open

Hardik K (hardikkaurani) wants to merge 2 commits into
microsoft:mainfrom
hardikkaurani:fix/multipart-omit-nil-headers-and-additional-values

Conversation

@hardikkaurani

Copy link
Copy Markdown
Contributor

Summary of Changes

  1. Omit Nil Header String Values in writeStringValue:

    • In writeStringValue(key, value), if key != null && !key.isEmpty() and value == null, it previously serialized key + "\r\n" without a colon or value, creating a malformed MIME header line that violates RFC 2046 and RFC 7230/5322 syntax.
    • Now checks if (key != null && !key.isEmpty() && value == null), returning immediately so that headers with null values are cleanly omitted, matching the Kiota serialization convention across SDKs.
    • Preserves delimiter emission when key == null || key.isEmpty() and value == null ("\r\n").
  2. Support additionalValuesToMerge in writeObjectValue:

    • Previously writeObjectValue validated Objects.requireNonNull(additionalValuesToMerge) but completely ignored the array without serializing any additional MultipartBody parts.
    • Now filters non-null additional values and iterates through them, verifying type (MultipartBody), invoking lifecycle callbacks (onBeforeObjectSerialization, onStartObjectSerialization, onAfterObjectSerialization), and serializing each part (consistent with FormSerializationWriter and JsonSerializationWriter).
  3. Unit Tests Added in MultipartSerializationWriterTests:

    • writesStringValueWithKeyValue: verifies standard header serialization (Content-Type: text/plain).
    • writeStringValueWithNilValueOmitsHeader: verifies headers with null values are omitted.
    • writeStringValueWithEmptyKeyAndNilValueWritesDelimiter: verifies delimiter emission (\r\n).
    • writeStringValueWithEmptyKeyAndValueWritesRawLine: verifies raw line serialization.
    • writesAStructuredObjectWithAdditionalValuesToMerge: verifies merging multiple MultipartBody parts.
    • throwsOnInvalidAdditionalValuesToMerge: asserts runtime exception for non-MultipartBody instances in additional values.

@hardikkaurani
Hardik K (hardikkaurani) requested a review from a team as a code owner October 25, 2026 04:51
Copilot AI balanced review requested due to automatic review settings October 25, 2026 04:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@hardikkaurani

Copy link
Copy Markdown
Contributor Author

Hi Vincent Biret (@baywet),

I've opened this PR in components/serialization/multipart to:

  • Omit headers when a key is provided with a null value in writeStringValue(), preventing malformed header lines without colon/value (Header\r\n).
  • Support additionalValuesToMerge in writeObjectValue() by iterating over and serializing additional MultipartBody parts with lifecycle callbacks (aligning with FormSerializationWriter and JsonSerializationWriter).

Unit tests covering header omission, additional values merging, and invalid part validation have been added, and spotlessCheck / test suites pass. Whenever convenient, could you please take a look? Thanks for your time!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Additional bodies are appended after the primary closing boundary, causing MIME parsers to ignore their parts instead of merging them.

3 open issues

Review effort: Balanced

if (onStartObjectSerialization != null) {
onStartObjectSerialization.accept(additionalValueToMerge, this);
}
additionalValueToMerge.serialize(this);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Serialized parts now share a single boundary (using the root body's boundary) with intermediate closing delimiters suppressed, emitting only a single final boundary delimiter. Replaced substring checks with boundary-aware assertions verifying the exact multi-part structure and ensuring no secondary boundaries leak.

Comment on lines +145 to +156
final byte[] bytes = result.readAllBytes();
assertEquals("\r\n", new String(bytes, "UTF-8"));
}
}
}

@Test
void writeStringValueWithEmptyKeyAndValueWritesRawLine() throws IOException {
try (final var serializationWriter = new MultipartSerializationWriter()) {
serializationWriter.writeStringValue("", "raw line");
try (final var result = serializationWriter.getSerializedContent()) {
final byte[] bytes = result.readAllBytes();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Replaced InputStream.readAllBytes() with Compatibility.readAllBytes() to preserve compatibility with Android API < 33.

Comment on lines +193 to +199
final MultipartBody part1 = new MultipartBody();
final TestEntity nonMultipart = new TestEntity();

try (final var serializationWriter = new MultipartSerializationWriter()) {
assertThrows(
RuntimeException.class,
() -> serializationWriter.writeObjectValue(null, part1, nonMultipart));

@hardikkaurani Hardik K (hardikkaurani) Oct 25, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Passed null for the primary body in throwsOnInvalidAdditionalValuesToMerge to isolate validation of additionalValuesToMerge, and added an assertion on the exact exception message.

Copilot AI balanced review requested due to automatic review settings October 25, 2026 14:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@hardikkaurani

Copy link
Copy Markdown
Contributor Author

Hi Vincent Biret (@baywet),

I have updated the PR in commit \29b7efa0\ to address the review recommendations:

  1. Shared Multipart Boundary Envelope: \writeObjectValue\ now serializes all parts from merged \MultipartBody\ instances under a single shared boundary (derived from the root body) and suppresses intermediate closing boundaries, ensuring only a single terminating delimiter (----) is emitted so parts are not treated as MIME epilogue.
  2. Android Compatibility in Tests: Replaced \InputStream.readAllBytes()\ with \Compatibility.readAllBytes()\ to support Android API levels < 33.
  3. Isolated Validation & Boundary-Aware Assertions: Separated \�dditionalValuesToMerge\ validation testing with explicit exception message assertions, and added boundary-aware assertions verifying the full multi-part stream and absence of secondary boundaries.

Local Gradle test suites and \spotlessCheck\ pass cleanly. Whenever convenient, please take another look. Thank you!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants