# Error and exit handling

Cliffy throws a `ValidationError` for invalid options, arguments and environment
variables. `ValidationError`s can also be thrown manually. By default, when a
`ValidationError` is thrown, cliffy prints the auto generated help and the error
message and calls `Deno.exit(validationError.exitCode ?? 1)` to exit the
program. This behaviour can be changed by calling
[`.throwErrors()`](#throw-errors) or [`.noExit()`](#no-exit) or by adding an
[error handler](#error-handler).

## Throw errors

By default, cliffy prints the help text of the failed command together with the
error message and calls `Deno.exit()` when a `ValidationError` is thrown. All
other errors will be thrown by default. You can override this behaviour with the
`.throwErrors()` method to always throw errors.

## No exit

The `.noExit()` method does the same as `.throwErrors()` but also prevents the
command from calling `Deno.exit()` for example when the help or version option
is called.

## Error handler

Errors can be caught by simply wrapping the `.parse()` method into a try catch
block. But you can also register an error handler with the `.error()` method.

The error handler is inherited by all nested sub-commands, at any depth. Child
commands can override error handlers from ancestor commands, in which case the
handler closest to the failed command is executed. If the error handler doesn't
throw or call `Deno.exit`, the default error handler is executed.

The first argument of the error handler is the error, the second argument is the
instance of the failed command, and the third argument is an `ErrorContext`
object. You can use the command instance to print the help text from this
command.

```ts
import { Command, ValidationError } from "@cliffy/command";

await new Command()
  .error((error, cmd) => {
    if (error instanceof ValidationError) {
      cmd.showHelp();
    }
    console.error(error);
    Deno.exit(error instanceof ValidationError ? error.exitCode : 1);
  })
  .action(() => {
    throw new ValidationError("validation error message.");
  })
  .parse();
```

### ErrorContext

The `ErrorContext` is the third argument of the error handler and contains the
options and arguments that were parsed at the time the error occurred. This is
useful when the error output should depend on the options the user passed — for
example a global `--verbose` flag that switches between a slim and a detailed
error message.

```ts
import { Command, ValidationError } from "@cliffy/command";

await new Command()
  .globalOption("-v, --verbose", "Enable verbose error output.")
  .error((error, cmd, ctx) => {
    if (error instanceof ValidationError) {
      cmd.showHelp();
      console.error(`\nerror: ${error.message}`);
    } else if (ctx.options.verbose) {
      // Detailed output: full stack trace when --verbose was passed.
      console.error(error);
    } else {
      // Slim output: just the message.
      console.error(`error: ${error.message}`);
    }
    Deno.exit(error instanceof ValidationError ? error.exitCode : 1);
  })
  .command("run <script:string>", "Run a script.")
  .action((_, script) => {
    throw new Error(`Script not found: ${script}`);
  })
  .parse();
```

```console
$ deno run example.ts run missing.ts
error: Script not found: missing.ts

$ deno run example.ts --verbose run missing.ts
Error: Script not found: missing.ts
    at ...
```

## Runtime errors

This example will catch only runtime errors.

```typescript
import { Command } from "@cliffy/command";

const cmd = new Command()
  .option("-p, --pizza-type <type>", "Flavour of pizza.")
  .action(() => {
    throw new Error("Some error happened.");
  });

try {
  await cmd.parse();
} catch (error) {
  console.error("[CUSTOM_ERROR]", error);
  Deno.exit(1);
}
```

```console
$ deno run examples/command/general_error_handling.ts -t
Unknown option "-t". Did you mean option "-h"?

$ deno run examples/command/general_error_handling.ts
[CUSTOM_ERROR] Some error happened.
```

## Validation errors

This example will catch all errors. You can differentiate between runtime and
validation errors by checking if the `error` is an instance of
`ValidationError`. The validation error has a `exitCode` property that should be
used to exit the program. It also provides a `cmd` property which references the
failed command.

```typescript
import { Command, ValidationError } from "@cliffy/command";

const cmd = new Command()
  .throwErrors() // <-- throw also validation errors.
  .option("-p, --pizza-type <type>", "Flavour of pizza.")
  .action(() => {
    throw new Error("Some error happened.");
  });

try {
  await cmd.parse();
} catch (error) {
  if (error instanceof ValidationError) {
    error.cmd?.showHelp();
    console.error("Usage error: %s", error.message);
    Deno.exit(error.exitCode);
  } else {
    console.error("Runtime error: %s", error);
    Deno.exit(1);
  }
}
```

```console
$ deno run examples/command/validation_error_handling.ts -t
Usage error: Unknown option "-t". Did you mean option "-h"?
```

### Custom validation errors

You can throw custom validation errors by throwing an instance of
`ValidationError`. Optionally you can define the exit code that should be used
when `Deno.exit()` is called after displaying the auto generated help and the
error message.

```typescript
import { Command, ValidationError } from "@cliffy/command";

await new Command()
  .option("-c, --color <name:string>", "Choose a color.")
  .action(({ color }) => {
    if (color === "black") {
      throw new ValidationError("Black is not supported.", { exitCode: 1 });
    }
  })
  .parse();
```

## Throw errors outside the command context

The `.throw()` method can be used to throw errors outside the command context so
you have the same behaviour as when you throw an error for example within an
action handler or a type.

```ts
import { Command, ValidationError } from "@cliffy/command";

const { options, cmd } = await new Command()
  .error((_error, _cmd) => {
    console.error("error handler...");
    // Throw or call Deno.exit() to disable the default error handler.
    // throw _error;
    // Deno.exit(_error instanceof ValidationError ? _error.exitCode : 1);
  })
  .option("-r, --runtime-error", "Triggers a runtime error.")
  .option("-v, --validation-error", "Triggers a validation error.")
  .parse();

if (options.validationError) {
  cmd.throw(new ValidationError("validation error message."));
}

if (options.runtimeError) {
  cmd.throw(new Error("runtime error message."));
}
```

```console
$ deno run examples/command/throw.ts --runtime-error
error handler...
error: Uncaught Error: runtime error message.
  cmd.throw(new Error("runtime error message."));
            ^
    at examples/command/throw.ts:21:13
```

```console
$ deno run examples/command/throw.ts --validation-error
error handler...

  Usage: COMMAND

  Options:

    -h, --help              - Show this help.
    -r, --runtime-error     - Triggers a runtime error.
    -v, --validation-error  - Triggers a validation error.

  error: validation error message.
```
