asyncapi / asyncapi/cli

[BUG] `asyncapi diff` prints empty `ValidationError:` on parse failure

Open
#2,222 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
272
Forks
375
Avg merge
3h 22m
Merged PRs (30d)
8

Description

## Symptom

When `asyncapi diff` is invoked with two documents and one of them fails to parse, the command exits with code `1` and prints a literal `ValidationError:` followed by no diagnostic body:

```
$ asyncapi diff old.yaml broken.yaml
...
ValidationError:
$ echo $?
1
```

This makes it impossible for users (and CI logs) to know why parsing failed.

## Reproduction

Repo is reproducible against `master` using the fixtures shipped in the repo:

```
$ git clone https://github.com/asyncapi/cli && cd cli
$ npm ci && npm run build
$ ./bin/run_bin diff test/fixtures/specification.yml test/fixtures/specification-invalid.yml
...
ValidationError:
$ echo $?
1
```

Confirmed on `master` (current 6.x) and v5.0.5.

## Root cause

Two contributing bugs in the parser-failure path:

1. **Wrong ValidationError type for parse failures** - [`src/apps/cli/commands/diff.ts`](https://github.com/asyncapi/cli/blob/master/src/apps/cli/commands/diff.ts) `parseDocuments` constructs:

```typescript
new ValidationError({
type: 'invalid-file',
filepath: firstDocument.getFilePath() || secondDocument.getFilePath(),
err: firstResult.error || secondResult.error,
})
```

`'invalid-file'` is the file-not-found template; it renders `There is no file or context with name "".` and ignores the `err:` field entirely. The other parse-failure path in the same file (the catch block at the bottom of `run()`) already uses `'parser-error'` correctly, so this is an internal inconsistency rather than a design choice.

2. **`buildError` emits an empty string for non-ParserError shapes** - [`src/errors/validation-error.ts`](https://github.com/asyncapi/cli/blob/master/src/errors/validation-error.ts) `buildError(err)` only inspects `err.title`, `err.detail`, and `err.validationErrors`. The current `ValidationService.parseDocument` returns its error as a plain string (`'Failed to parse document'`), so none of those checks match and `this.message` stays `''`. Even with fix #1 applied, the outer `catch` in `run()` wraps the original `ValidationError` in another one with `type: 'parser-error'`, and `buildError` once again sees no `title` / `detail` / `validationErrors` - so the message is still empty by the time oclif prints it.

The combination of these two bugs is what produces the bare `ValidationError:` symptom.

## Related prior art

- #2093 - `BUG: Diff command doesn't work when circular reference`. Different code path (circular `$ref` throws a `TypeError` during JSON serialisation rather than failing the parser's `success` check), but same general class: parse-time failures aren't surfaced gracefully by `diff`.
- #2113 / #2167 - earlier PRs attempting to handle the circular-ref case. Closed without merge; maintainers asked for tests.

## Proposed fix

I have a PR ready that:

1. Switches the `parseDocuments` failure branch from `'invalid-file'` to `'parser-error'`, mirroring the existing usage in the `run()` catch block.
2. Adds a fallback in `buildError` so any error shape (string, generic `Error`, or undefined) produces a non-empty message instead of `''`.
3. Adds a unit test for `ValidationError` (no test file exists today) and an integration test for `diff` against the parse-failure path.

I'll open it shortly and link it here.

## Environment

- `@asyncapi/cli` master (current 6.x) and v5.0.5
- Node 24, npm 11
- macOS, but the bug is platform-independent (lives in CLI-side error routing)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.