swift-argument-parser icon indicating copy to clipboard operation
swift-argument-parser copied to clipboard

Return codes for custom error types

Open finestructure opened this issue 4 years ago • 4 comments

I have a custom error type that I'm throwing from my command:

enum Error: LocalizedError {
  case foo
  ...

  var localizedDescription: String {
    switch self {
      case .foo: return "foo error"
      ...
    }
  }
}

These all surface with code 1 in the shell. I'd like at least some of them to report specific codes so I can more easily respond to them.

I've seen there's ExitCode which I could throw instead but then I don't have a single Error tracking all error states anymore and I'd need to make the error printing local to the throwing location because ExitCode does not do any error logging:

print("error bar")
throw ExitCode(2)

Currently I can just throw and tweak all the error messages in my Error's localizedDescription.

I tried making my Error adopt RawRepresentable in the hopes it might pick up the raw values from there but it didn't.

Is there another way to attach specific codes to custom error types? Would it be possible to pick up error codes from RawRepresentable or some other error code protocol?

finestructure avatar Aug 26 '20 07:08 finestructure

Hi there!

You can always conform to a CustomNSError protocol.

enum MyError: Error, LocalizedError, CustomNSError {
	case firstCase
	case secondCase

	var errorDescription: String? {
		switch self {
		case .firstCase:
			return "Here is an error description for the first case"
		case .secondCase:
			return "Here is an error description for the second case"
		}
	}

	var errorCode: Int {
		switch self {
		case .firstCase:
			return 101
		case .secondCase:
			return 102
		}
	}
}

SergeyPetrachkov avatar Sep 30 '20 03:09 SergeyPetrachkov

Just opened #243, since @SergeyPetrachkov's suggestion isn't actually supported yet.

natecook1000 avatar Oct 01 '20 15:10 natecook1000

Please do not (ab)use the errorCode of any general-purpose Error type/protocol as the exit status -- it leads to issues like #269.

A better way to do this is to introduce a mixin Error protocol with an explicit exitCode property requirement, and/or careful runtime validation to prevent exiting with a truncated status.

lorentey avatar Feb 09 '21 07:02 lorentey

On reflection, FWIW, I don't think the exit status is the best place to differentiate between the myriad possible error cases -- the output sent to stderr already does a better job at that.

Whether the process failed due to POSIXError.ENOENT or CocoaError.fileReadNoPermission is in the vast majority of cases irrelevant to the parent process; and trying to map all these errors into a tiny range of possible integers is an impossible problem in the first place.

Authors of command line tools ought to be in complete control of the exit status of their program. A CustomExitStatus protocol is one way to do this; but allowing them to set a custom closure (or implement a ParsableCommand method) that maps an Error value to an exit code might be even better in the long run. (After all, in most cases, tools would just want to be able to pick a particular value for all errors.)

One issue with the protocol approach is that library authors would likely start "helpfully" conforming their error types to CustomExitStatus, inappropriately taking control out of the hand of the tool author.

Another issue is that we'd still need to come up with a way to customize the exit status for errors that don't implement CustomExitStatus. Defaulting to, say, 1 (like the package does now) isn't going to work in cases like grep where the tool needs to reserve the 1 exit code for non-erroneous failures.

lorentey avatar Feb 09 '21 19:02 lorentey