Skip to content

feat: carry structured location and exception cause in api errors - #436

Open
webern wants to merge 1 commit into
m/mxdev-raiifrom
m/mxdev-errinfo
Open

feat: carry structured location and exception cause in api errors#436
webern wants to merge 1 commit into
m/mxdev-raiifrom
m/mxdev-errinfo

Conversation

@webern

@webern webern commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Human Summary

TODO: human writes here

Summary

internalError was a dead end: it swallowed the exception type, said nothing about where the problem was, and a TODO in Result.h said so. This gives ApiError two things it was missing: a place and a cause.

Location says where, in whichever world the error happened:

struct Location
{
    std::string xmlPath;  // /score-partwise/part[1]/measure[3]/note[2]
    int partIndex = -1;         // ScoreData coordinates; -1 = unknown
    int measureIndex = -1;
    int staffIndex = -1;
    int voiceIndex = -1;
    int tickTimePosition = -1;
    long long byteOffset = -1;  // raw XML syntax errors
};

cause (std::exception_ptr) keeps the actual exception that was caught, so a caller can rethrow it, inspect its type, or read its message; std::bad_alloc now maps to its own ResultCode::outOfMemory instead of hiding in internalError. The catch blocks in MusicXml.cpp capture std::current_exception() into cause.

Two sources of position are wired now, without threading any new context through the call stack:

  • The WriteRefusal sites in NoteWriter were already holding a MeasureCursor (part, measure, staff, voice, tick) when they refused a note; the error now carries it. A ninth beam comes back as tooManyElements at part=0 measure=0 staff=0 voice=0 tick=0.
  • XML syntax errors carry pugixml's byte offset. Parse/schema errors keep the element path they already had; the io errors say the file path in their message instead of pretending it was an XML path.

formatError(const ApiError&) renders an error the same way every time (mx: tooManyElements at part=0 measure=0: ...), for logs, tests, and anyone (or anything) reading test output.

The path field is replaced by location; aggregate initialization of ApiError changes shape, so this is breaking, but only for code that constructs errors directly. mx::core is untouched: the core mirror maps Error::path into location.xmlPath and stays lossless.

This is groundwork for #432: a warnings mechanism wants somewhere to say where and something to attach; this interface now has both.

Testing

  • ResultTest (new): Location defaults, formatError output pinned for bare codes, XML paths, score coordinates, and byte offsets
  • tooManyElementsCarriesThePlaceInTheScore: a 9-beam note refuses with the full cursor position, rendered by formatError
  • outOfMemoryIsReportedNotThrown: a std::bad_alloc thrown mid-parse comes back as outOfMemory, rethrowable from cause
  • internalErrorKeepsTheException: an unexpected exception comes back as internalError with the original type and message preserved in cause
  • Full suite passes: 5525 assertions in 610 test cases
  • make api-roundtrip regression: 414 passed, 0 failed
  • make fmt-check passes

References

ApiError now says where and keeps what. Location holds the place in the
XML (element path, byte offset) or in the score (part, measure, staff,
voice, tick); the WriteRefusal sites stamp it from the cursor already
in scope. std::bad_alloc reaching the boundary is reported as
ResultCode::outOfMemory; every other caught exception is internalError
with the exception itself kept in std::exception_ptr cause.
formatError renders an error the same way every time.
@webern webern added feature new feature request breaking fixes or implementation that require breaking changes api Affects the mx::api layer ai Issues opened by, or through, a coding agent. labels Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai Issues opened by, or through, a coding agent. api Affects the mx::api layer breaking fixes or implementation that require breaking changes feature new feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant