Skip to content

openapi: raw preservation rewrites timestamp and binary scalars, losing source spelling and bytes #242

Description

@OmarAlJarrah

Summary

annotation.RawFromNode renders two scalar tags in a form that does not carry what the source wrote, in the channel whose documented promise is verbatim preservation (ir.UnmodeledEntry.Value). This affects every raw-preservation site: x-* extensions, oneOf/anyOf sibling preservation, the §4.7 validation-only carve-out, and tuple items-after-prefix.

Timestamps are normalized to RFC 3339, so the source spelling is gone:

x-effective: 2021-1-1

is preserved as "2021-01-01T00:00:00Z". The value is still a date, but 2021-1-1 is not recoverable, and the construct silently acquires a time and a zone the source never wrote.

Binary scalars are base64-decoded and the decoded bytes are then stored as a JSON string, which is lossy twice over:

x-blob: !!binary aGVsbG8=      # preserved as "hello" — the base64 spelling is gone
x-raw:  !!binary /w==          # preserved as "�" — the byte 0xFF is destroyed

The second case is outright data loss: 0xFF is not valid UTF-8, so encoding/json replaces it with U+FFFD. Nothing downstream can tell that from a source that really did write U+FFFD.

Root cause

compilers/openapi/internal/annotation/rawjson.go, the !!timestamp and !!binary arms of scalar. Both reproduce what a whole-tree node.Decode(&any) used to do, which is how the compiler has always behaved — they were left alone when the numeric arms were changed to read source text (#32), because they are a different mechanism and deserve their own reasoning rather than being swept along.

Expected

Decide, and write down, what "verbatim" means for a scalar JSON has no type for. The plausible answer for both is the source spelling — 2021-1-1 stays 2021-1-1, and a !!binary keeps its base64 text — which is what the sibling value.FromNode channel already does for timestamps.

Whichever way it goes, ir.UnmodeledEntry.Value's doc comment should state it, since that comment is where a consumer learns what the field guarantees.

Notes

  • value.FromNode (the ir.Value channel) already keeps a timestamp's verbatim source spelling, so the two channels disagree today for the same source scalar.
  • Changing either arm will move testdata/conformance/openapi/yaml-timestamp-scalars.* goldens; the fixture already exists.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions