Skip to content

fix(map): return items as the declared type from MapOperation - #874

Merged
yaythomas merged 4 commits into
aws:mainfrom
pcbeingused333:c0763-map-item-type
Nov 4, 2026
Merged

yaythomas merged 4 commits into
aws:mainfrom
pcbeingused333:c0763-map-item-type

Conversation

@pcbeingused333

Copy link
Copy Markdown
Contributor

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Issue Link, if available

Fixes #870

Description

MapOperation returned its items as LinkedHashMap instead of the declared item type. The superclass was given new TypeToken<>() {} created inside the generic MapOperation<I, O>, so the captured type was MapResult<O> with O still a type variable and Jackson had no item type. This also affects the first execution, because MapOperation replaces its result with the serialize-then-deserialize copy. The fix builds the MapResult<O> type from the itemResultType the constructor already receives, and adds TypeToken.of(Type) (the approach suggested in the issue).

Demo/Screenshots

Not applicable.

Design

  • Design note (rule 3): the public API addition (TypeToken.of(Type)) is the fix suggested in [Bug]: ctx.map returns MapResult items as LinkedHashMap instead of the declared result type #870.
  • Simpler options considered (rule 2): none simpler; the type has to be built from the runtime TypeToken<O>, and the anonymous-class capture cannot see it.
  • Root cause (rule 5): the anonymous TypeToken in the generic MapOperation captures an unresolved type variable, so Jackson deserializes items as LinkedHashMap.
  • Net change in source lines and in public API members: +51 / -1 source lines, +1 public API member (TypeToken.of).

Assumptions

  • Jackson's TypeFactory.constructType accepts a custom ParameterizedType implementation. Evidence: MapIntegrationTest passes with record items on first execution and replay.
  • JacksonSerDes caches by Type in an unbounded map, so the new type needs value equality or each operation leaks an entry. Evidence: JacksonSerDes.typeCache (computeIfAbsent); the type is a record and MapOperationTypeTest checks equals/hashCode.

Decisions not covered by the issue or the design note

  • The ParameterizedType is a private record in MapOperation: records give equals/hashCode for free, and nothing else needs it.

Checklist

  • I have filled out every section of the PR template
  • I have thoroughly tested this change
  • Every new branch has a test that fails without it (rule 4)
  • For a bug fix: the description or commit order shows the regression test failing before the fix and passing after it (rule 5)
  • Every edited test expectation is listed in the description, with the reason (rule 5): none edited

Testing

Unit Tests

Yes. MapOperationTypeTest checks that two mapResultType(x) tokens are equal with equal hash codes, and differ for a different item type.

Integration Tests

Yes. MapIntegrationTest.testPojoItemsKeepDeclaredTypeOnFirstExecutionAndReplay maps to a record type and reads the items as that type on the first execution and on replay. It fails without the change and passes with it; the full class (110 tests) passes.

Examples

Not applicable.

This change was prepared with the assistance of an AI coding tool (Claude) and reviewed before submission.

@pcbeingused333
pcbeingused333 requested a review from a team November 3, 2026 21:41
@pcbeingused333
pcbeingused333 temporarily deployed to ai-pr-review-runtime November 3, 2026 21:41 — with GitHub Actions Inactive

@zhongkechen zhongkechen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for your contribution. Left a comment for the new ParameterizedType.

@yaythomas yaythomas added the needs-review Awaiting maintainer review label Nov 3, 2026
Comment on lines +69 to +71
public static <U> TypeToken<U> of(Type type) {
return new TypeToken<>(type) {};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_exti74gecpzis5h27y5gqj3mzd

[P2] Complete the design alternatives for the public API addition. Rules 2.5 and 3.3 require concrete alternatives, including simplification of existing logic, and reasons for rejecting them. The description explains why the existing anonymous capture fails, but citing #870 and stating “none simpler” does not evaluate an alternative or justify exposing a general public factory. This introduces a lasting API contract without the required scope analysis. Please return to the design phase under rule 8, step 6, and document the alternatives and chosen API scope before proceeding.

To suppress repeats, reply /ai verdict deferred, /ai verdict false-positive, or /ai verdict out-of-scope on the first nonblank line. Add rationale on later lines. A new /ai verdict accepted allows future repeats again.

Comment on lines +139 to +140
&& getRawType().equals(that.getRawType())
&& that.getOwnerType() == null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_yuqlni6oon2q5cnp3sjkqll4se

[P2] Revisit the equality conditions under rule 4. The new equality tests compare only parameterized MapResult types with null owners; their negative case changes only the item type. Removing either the raw-type comparison or the owner check leaves those assertions unchanged, so these added conditions lack the behavior-distinguishing tests required by “Every branch has a test that fails without it.” The claimed cache-key contract is therefore incompletely guarded. Please return to the design phase under rule 8, step 6, and establish which conditions the contract needs and how each will be demonstrated.

To suppress repeats, reply /ai verdict deferred, /ai verdict false-positive, or /ai verdict out-of-scope on the first nonblank line. Add rationale on later lines. A new /ai verdict accepted allows future repeats again.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Codex AI review

Two approach findings: incomplete design alternatives at sdk/src/main/java/software/amazon/lambda/durable/TypeToken.java:69 and untested equality conditions at sdk/src/main/java/software/amazon/lambda/durable/operation/MapOperation.java:139. Tests were not executed during this read-only review.

Reviewed commit bcc9c3b7a269f36433b03579f14a84970bad143c. Workflow run

* @param <U> the type
* @return a TypeToken representing the given type
*/
public static <U> TypeToken<U> of(Type type) {

@yaythomas yaythomas Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The type parameter U does not appear in the argument, so TypeToken<String> t = TypeToken.of(Integer.class) compiles, and a null argument is accepted and fails later. Since of is new and unreleased, we can still think about the signature. Could this return TypeToken<?>, call Objects.requireNonNull(type, ...), and keep the unchecked cast inside MapOperation.mapResultType? (Maybe also have a direct test of this case?)

The type parameter did not appear in the argument, so a caller could pick
any T, and a null type failed later. Keep the unchecked cast inside
MapOperation.mapResultType and add direct tests for of().
@pcbeingused333

Copy link
Copy Markdown
Contributor Author

Done in e567ed3: of() now returns TypeToken<?>, requires non-null, and the unchecked cast lives in MapOperation.mapResultType; added direct tests.

This comment was drafted with Claude Code.

@yaythomas yaythomas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

:shipit: Thank you so much for the quick turnaround, @pcbeingused333! 🙇

@yaythomas
yaythomas merged commit 46d657d into aws:main Nov 4, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review Awaiting maintainer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: ctx.map returns MapResult items as LinkedHashMap instead of the declared result type

3 participants