Skip to content

fixup json snaps and deser - #692

Merged
lucix-aws merged 2 commits into
mainfrom
fix-response-snaps
Aug 7, 2026
Merged

fixup json snaps and deser#692
lucix-aws merged 2 commits into
mainfrom
fix-response-snaps

Conversation

@lucix-aws

Copy link
Copy Markdown
Collaborator

fixup all awsjson/restjson response snapshot issues

serde bugs

  • dynamically cache collection members instead of trying to precache them on AddMember, this doesn't work when recursive structures call AddMember on some collection before its own members are set
  • don't serialize unset json document as null

snapshot bugs

  • response snapshots have to actually have input because of httpbinding path validation
  • actually test @streaming blobs in payloads
  • compare snapshots by structure rather than bytes, because legacy and schema-serde json order struct members differently
  • errors
    • the error discriminator is spliced into the body instead of decode/mutate/re-encode, to prevent more payload shuffling
    • json errors carry __type in the body instead of X-Amzn-ErrorType. the header is the highest-precedence source, so it was the only one we ever covered, and the body is what services actually send.
    • legacy resolves body code before __type and schema-serde does the opposite, so a placeholder there resolves to a different type under each, to make them consistent, I just made any code field in an error member also carry the error shape name
  • skip operations that cannot be serialized symmetrically. see source comment. This only amounts to ~20 operations over the 10k+ we have so I think it's fine
  • CompareValues had no reflect.Interface case, so union and document fields fell through to DeepEqual and skipped the reader/document comparators

@lucix-aws
lucix-aws requested review from a team as code owners August 6, 2026 15:38
for (var operation : operations) {
var inputSymbol = symbolProvider.toSymbol(model.expectShape(operation.getInputShape()));
for (var operation : allOperations(model, service)) {
var reason = skipReason(model, operation, eventStreamIndex);

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.

praise: I like this reason mechanism

private static boolean isCbor(GoSettings settings) {
return Rpcv2CborTrait.ID.equals(settings.getProtocol());
// Emits serdeBodyEqual, the request-body comparator, per protocol. Every variant falls back to a byte compare
// when either side won't decode, so a malformed golden fails loudly rather than silently passing.

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.

what's a malformed golden?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

golden = snapshot file

""");
}
// rpcv2Cbor encodes struct fields as a CBOR map and the encoder emits map entries in Go map iteration order, so
// the same input produces different byte orderings across runs. Comparing decoded values is order-independent.

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.

we don't have to resolve it here, but this has also been a point of friction on JSON encoders. Since we are now taking more control of the encoders, would it be worth it to sort map entries?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

by default, a million percent no, it's bad for performance,
as an option, such that we could turn it on for snapshots - 100% and i intend to do so. we have an open issue for this over in the sdk.

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.

ok, as an option it sounds good.

by default, a million percent no, it's bad for performance

Can't argue with this, but we've had multiple issues with serialization not being deterministic being an issue even if it's not a guarantee for JSON, so it's definitely a tredoff.

Interestingly, JSON v2 switched this behavior and now map keys are not deterministic by default and you have to pass a deterministic flag https://pkg.go.dev/encoding/json/v2#Deterministic

Comment thread schema.go
// A test that exists only to record, in the generated SDK, that an operation or error has no snapshot coverage.
private Writable writeSkip(String testName, String reason) {
return goTemplate("""
func $name:L(t *$testingT:T) {

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.

same, like this placeholder test with operation in signature and reason in log

@lucix-aws lucix-aws closed this Aug 7, 2026
@lucix-aws lucix-aws reopened this Aug 7, 2026
@lucix-aws
lucix-aws merged commit f391c60 into main Aug 7, 2026
21 of 38 checks passed
@lucix-aws
lucix-aws deleted the fix-response-snaps branch August 7, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants