fix(engine)!: keep compiler diagnostics and exit on spec problems - #335
Merged
Conversation
Run replaced the compiler's returned diagnostics with the document's own list, so a compiler that reports without also storing lost every finding at every severity. It now merges the two and writes the union to both. An undecodable file, an unrecognized format, Swagger 2.0 and a version no compiler claims all left Run as Go errors, which the CLI renders as exit 2 — the code a bad flag gets. They are problems with the spec, so they now come back as diagnostics with a nil document and exit 1, and the usage code is left to actual misuse.
The README conflict this branch predicted, plus one it could not: #312 added a validate command whose TestRun_ValidateEngineFailures asserts the old taxonomy — an unrecognized format exiting 2. This change makes that a diagnostic and exit 1, so the row had to move with it. It now asserts the code, and a comment says why the two rows in that table exit differently: a format nothing recognizes is a problem with the spec, an unreadable file is a problem with the invocation. Both commands land on the new behaviour identically, since validate reaches it through the shared pipeline rather than a second copy. README: #312's two-command flag table already sits above the conflict, so this branch's pre-validate table is dropped and its new prose kept — the three location forms and the corrected exit-code taxonomy — reworded to keep #312's "both commands" framing. Two tests are gone against main and both are this branch's own deletions, not the merge's: TestEngine_RunSniffError and TestRun_ParseUnknownSpecFails assert the Go-error behaviour being inverted, and are replaced by TestEngine_RunSniffProblemsAreDiagnostics and TestRun_SpecProblemsExitOne.
OmarAlJarrah
added a commit
that referenced
this pull request
Aug 9, 2026
The three conflicts this branch predicted, resolved as it said: compile.go keeps #312's specOptions split and gains the pretty field, command_test.go keeps #312's two-list block and gains "pretty", and the README keeps #312's two-command flag table with the -o wording changed and a --pretty row added. #335 also landed on the README since, so its diagnostics and exit-code prose is kept whole rather than reverted to this branch's older paragraph. One interaction the branch could not predict: #330's TestRun_TerminatorAsFlagValue asserts the artifact contains `"name": "Tiny"` — the indented spelling. That test is about which argument "--" became, not about formatting, and -o is compact now, so it matched text it never meant to pin. It decodes the artifact instead, which is what it was always asking.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two defects in how a spec problem travels out of the engine, fixed together because they share a seam.
Diagnostics were replaced instead of merged.
engine.Runrandiags = doc.Diagnosticsafter asuccessful compile, so a compiler that returns its findings without also storing them on the document
lost all of them, error severity included. The one compiler in the tree fills both lists, which is
why nothing caught this: the only stub that returned unstored diagnostics returned a nil document
too, and a nil document short-circuits one branch earlier. The direction is the perverse part —
with the validate pass enabled (the default) the finding count went to zero, and with
--skip-validateit did not. Turning validation on removed findings, and the CLI then exited 0 ona spec its compiler had refused outright.
Runnow merges the two lists and writes the union to both, so a finding survives whichever channelits compiler used.
compilers.Compiler's doc comment now says what each list is, since the enginewas relying on an agreement the contract never stated.
Spec problems escaped as Go errors and became usage exit codes. An undecodable file, an
unrecognized format, Swagger 2.0, and a version no compiler claims were all
fmt.Errorfout ofRun, andcmd/morphicmaps any Go error to exit 2 — the code a bad flag gets. Somorphic compile v4.yamlansweredno compiler registered for format openapi@4.0with exit 2 whilethe compiler's own
openapi/unsupported-versiondiagnostic for those exact bytes stayed unreachablefrom the shipped binary. A CI wrapper telling "this spec is broken" from "you invoked the tool
wrong" misclassified every one of them.
Sniffreturns a diagnostic and a comma-ok instead of an error — nothing it can hit is an I/O or aprogrammer error — and
Runreturns the shape it already blesses as legal: a nil document alongsidediagnostics. Four codes in the engine's own namespace:
engine/undecodable-source,engine/unrecognized-format,engine/unsupported-format,engine/no-compiler-for-format. The CLIneeded no exit-code change; a nil document was already exit 1.
Exit-code taxonomy
0--fail-on. Also every help request.1--fail-on, or it produced no document at all — undecodable, unrecognized format, unsupported format, no compiler for its version.2Nothing about a spec's own contents reaches
2. This is the taxonomy README already documented andthe code did not implement. README's wording is tightened to name what falls in each bucket, and to
describe the diagnostic line's location field now that it has a third form — absent, for a finding
raised before any document existed, which is what these new codes carry.
Notes on adjacent work
openapi: 4.0.0still sniffs toopenapi@4.0and misses theregistry;
openapi: truestill yieldsopenapi@true. engine+compilers: move format detection to a compiler-owned seam #68 owns moving detection to acompiler-owned seam. This settles only how the outcome is reported — a diagnostic rather than a
Go error, exit 1 rather than 2 — so whatever shape detection ends up taking, the reporting
contract is already fixed under it.
front/frontsidentifiers and theengine: parse %qprefix inengine/engine.goare leftas they are; docs: architecture and README drift from the implementation #64 records those as its rename to make.
produce and does not close that item.
cmd/morphic's dispatch and the same README paragraph; whichever landssecond has a small textual merge there.
Test plan
engine:TestEngine_RunKeepsDiagnosticsFromEitherChanneldrives a stub compiler whose stored andreturned lists are set independently, across both
SkipValidatemodes. Unlike the existingnilDocCompilerit hands back a non-nil document, so it reaches the fold rather than stopping abranch short of it. Three of its six rows fail on the old code.
engine:TestEngine_RunSniffProblemsAreDiagnosticsandTestEngine_RunLookupMissassert adiagnostic, a nil document, error severity and
ir.NoSourceprovenance in place of a Go error.TestSniff_Formatsasserts codes rather than error substrings.cmd/morphic:TestRun_SpecProblemsExitOneruns the real CLI over one spec per class and assertsexit 1, the code on stderr, empty stdout, and no usage block.
cmd/morphic:TestRenderDiagnostics_WithAndWithoutSourcePathpins all three rendered locationforms as whole lines rather than fragments.
matching test went red every time and green again on restore.
gofmt,go vet,golangci-lint,go build, coverage at 100%.Breaking
engine.Sniffchanges signature from(compilers.SourceFormat, error)to(compilers.SourceFormat, ir.Diagnostic, bool); it is exported, so an embedder calling it directlymust adapt.
engine.Runno longer returns a Go error for the four classes above — it returns a*Resultwith a nilDocumentand diagnostics — andmorphic compileexits 1 rather than 2 forthem, which anything scripting the CLI will observe.
Closes #56
Closes #58