Skip to content

Port the capture aborted-union detail: a wrong-typed field answers Invalid input, not the field message #979

Open

Description

@chappie-daemon

The Rust decoder's NOTICE for a malformed client frame does not reproduce the capture's
error model. Both implementations refuse these frames; only the detail after invalid:
differs.

Measured divergence

nostream_core::messages::decode (crates/nostream-core/src/messages.rs) reports the
failing field's own message. The capture's messageSchema.parse over a z.union
(src/schemas/message-schema.ts:61, zod 3.25.76) reports one invalid_union issue whose
message is Invalid input, because a wrong-typed member aborts the branch instead of
recording an issue on it.

frame port (WireError::Validation) capture (issues[0].message)
["EVENT",{…,"created_at":"1700000000",…}] Expected number, received string Invalid input
["EVENT",{…,"created_at":null,…}] Expected number, received null Invalid input
["EVENT",{…,"content":5,…}] Expected string, received number Invalid input
["EVENT",{…}] with a missing sig Expected string, received null Invalid input
["EVENT",{…,"tags":{},…}] Expected array, received object Invalid input
["REQ","s1",{"unknown":1}] Expected array, received number Invalid input
["REQ","s1",{"kinds":[[1,2]]}] Expected number, received array Invalid input
["REQ","s1",{…},"junk"] Expected object, received string Invalid input
["CLOSE",42] Invalid input Invalid input (already agrees)

Constraint failures — a branch that parses and then records an issue — do agree today:
id uppercase → Invalid, a 63-character id → String must contain exactly 64 character(s), created_at: -1 → Number must be greater than or equal to 0,
{"limit":-1} → the same, {"unknown":["x"]} → Unknown key: unknown, an unknown event
key → Unrecognized key(s) in object: 'extra'. That is the same aborted-versus-recorded
split, so the fix is a classification, not a set of string edits.

What closing it costs

Every issue validate_event_value (crates/nostream-core/src/messages.rs) and
validate_filter (crates/nostream-core/src/filter.rs) produce has to be classified as
aborted (report Invalid input) or recorded (report its own message), and the decoder
has to answer the first class as the union does. That rewrites the decoder's error model.

Two consequences ride with it:

  • decode_reports_the_capture_notice_strings in
    crates/nostream-core/tests/wire_codec.rs pins two port-only rows
    (Expected array, received number, Expected number, received array); those rows move
    in the same change.
  • The same model decides issue order inside a frame that is both over-long and dirty:
    ["CLOSE","","junk"] answers String must contain at least 1 character(s) here and the
    tuple's Array must contain at most 2 element(s) there, and
    ["EVENT",{…,"kind":1.5},"junk"] answers Expected integer, received float here and
    Array must contain at most 2 element(s) there.

Where the port records this today

crates/nostream-core/src/messages.rs's module doc is the authoritative list of the
decoder's differences from the capture; it names this gap as open, with the capture
authoritative.

No frame is admitted by one implementation and refused by the other on account of this
class; the NOTICE text is what differs.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrewritenostream 4.0 Rust/LMDB rewriterustRust implementation work

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions