Skip to content

feat(renderer)!: propagate errors from render callbacks - #324

Closed
sundermann wants to merge 1 commit into
Bergmann89:masterfrom
sundermann:feat/fallible-renderer-callbacks
Closed

sundermann wants to merge 1 commit into
Bergmann89:masterfrom
sundermann:feat/fallible-renderer-callbacks

Conversation

@sundermann

Copy link
Copy Markdown
Contributor

Allows custom render steps to return errors directly instead of storing failures for later reporting. Rendering stops at the first error and preserves its source chain.

Changes

  • Propagate callback failures through Renderer, exec_render, and generate, discarding partial output.
  • Add diagnostics identifying the render step, callback phase, and schema type.
  • Update built-in steps and examples without changing generated output.
  • Test callback ordering, early termination, default callbacks, and error source chains.

Breaking changes: RenderStep::initialize, render_type, and finish now return Result<(), renderer::Error>. Custom implementations must return Ok(()) on success. Renderer::finish now returns Result<Module, renderer::Error>, which callers must handle. The error enum adds Custom and RenderStep variants, requiring updates to exhaustive matches. Finish callbacks do not run after an earlier failure.

Checklist

  • Tests for the change (a bug fix has a test that fails without the fix)
  • Changed snapshots are regenerated with update-expectations and reviewed
  • cargo fmt, cargo clippy and cargo test pass (see CONTRIBUTING.md)
  • Breaking changes of the public API or the generated code are described above

Custom render steps currently have to save callback failures in shared
state and report them after rendering. Return renderer errors from every
callback and stop at the first failure instead, discarding partial
output.

Preserve application error sources through a custom error wrapper and
add the step name, a typed callback phase, and the schema type to
diagnostics. Keep the callback's renderer error available as a typed
source. Report cause text through the source chain without repeating it
in each wrapper.

Update built-in steps and custom-renderer examples without changing
their generated code. The VSME callback now reports a missing type path
through the renderer instead of panicking. Cover all callback phases
through the low-level renderer and public generation functions,
including callback ordering, short-circuiting, source chains, and
default callbacks.

BREAKING CHANGE: RenderStep::initialize, render_type, and finish return
Result<(), RendererError>. Renderer::finish returns Result<Module,
RendererError>. Existing custom implementations must return Ok(()) on
success, and direct Renderer callers must handle the result.
RendererError adds Custom and RenderStep variants; exhaustive matches
must handle them.

@Bergmann89 Bergmann89 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the PR! Before we go into the details I'd like to understand the use case, because this goes against an idea the pipeline is built on.

The generator is meant to take care of everything that can fail, so the renderer can render the code without errors afterwards. That's why Renderer::finish is infallible today: the only renderer errors come from validating the config in exec_render, before anything is rendered. With this PR every callback becomes fallible, which is a breaking change for all custom steps and would require a new major version, while all built-in steps just return Ok(()).

So the change only helps custom steps that do something fallible on their own (I/O, external lookups, ...). These can already validate their input up front, or keep the error in shared state (e.g. an Arc<Mutex<Option<Error>>>) and check it after generate returned.

What is the concrete case you ran into? If there is one that can't be solved this way, I'm happy to discuss it, but I'd rather not make the whole render step API fallible for it.

@sundermann

Copy link
Copy Markdown
Contributor Author

Thanks, that makes sense. Our concrete case is documentation cleanup step that parses the rendered module with syn::parse2. The PR removes shared-error plumbing, but we can run that pass as fallible postprocessing after rendering instead. I agree this doesn’t justify breaking the render-step API, so I’ll keep that handling local.

@sundermann sundermann closed this Nov 12, 2026
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.

2 participants