Struct MyStruct: Error does not conform to _ErrorCodeProtocol

I was recently refactoring some custom Error type code when I ran into a strange issue. Where I could not pattern match against Errors (with catch ErrorType.error) because my type does not conform to the _ErrorCodeProtocol.

Some background:

My project is built around POSIX API calls and therefore most of the errors are based off of the POSIX errno.

I want errors to be strongly typed based on the type of action being performed in order to aid in debugging.

Explanation

ie:

let newFile: Open<FilePath> = try FilePath("/path/to/new/file").create()
try newFile.write(FilePath("/path/to/file").open(permissions: .read))

Both the above .write() call and .read() within the .write() could throw a permissions denied error. In order to distinguish between which of the calls is actually throwing, I have typed errors ReadError and WriteError, both with a permissionDenied error.


I have a protocol which conforms to the Error protocol:

// I use CaseIterable to aid with throwing when a C API returns unsuccessfully
public protocol ProjectNameError: Error, CaseIterable where AllCases == [Self] {
    // Store an Array since sometimes multiple POSIX error codes may relate to a 
    // single error (ie: EAGAIN and EWOULDBLOCK both correlate to 'would block'
    // errors see `read(2)` and `write(2)`)
    var errors: [ErrNo] { get set }

    init(error: ErrNo?)
}

extension ProjectNameError {
    public static var permissionDenied: Self { return Self(errors: EACCES, EPERM) }
    // Lots more common POSIX errors

    // Helper initializers and functions

    public static func getError() -> Self {
        // ErrNo.lastError is the current value of the errno
        return Self.allCases.filter({ $0.contains(ErrNo.lastError) }).first ?? .unknown
    }
}

And several structs that inherit from my custom protocol similar to this:

public struct ReadError: ProjectNameError {
    // Define read(2) specific errors

    public static let allCases: [ReadError] = [
        .permissionDenied, // the rest of the custom errors for read(2) API calls
    ]
}

My project uses the CommonPOSIXError protocol to throw like so:

extension Open where PathType == FilePath {
    public func read() throws -> String {
        // blah blah blah

        guard cRead(fileDescriptor, buffer, bytes) != -1 else {
            // .getError() makes throwing a lot easier since multiple types could 
            // reuse ReadError without having to write a big switch for all the
            // possible read(2) errors
            throw ReadError.getError()
        }

        // store buffer's bytes into String(cString)

        return str
    }

Previously, I had getError() -> Self as part of the ProjectNameError protocol definition and all of my Error types were enums that implemented getError() using a switch statement. I meant a lot of duplicated code, so I've been refactoring to reduce the duplicated code. (My Errors.swift file went from 645 lines down to 357 after my refactor using the above style)

Everything in my Errors.swift file now compiles, however, everywhere I try to pattern match:

do {
    let openFile = try file.create()
catch OpenError.pathExists { // Argument type 'OpenFileError' does not conform to expected type '_ErrorCodeProtocol'
    // stuff
}

This is weird because the Swift documentation states that the Error protocol "has no requirements of its own" so I shouldn't have to implement anything else. Why is it requiring conformance to an underscored protocol in Foundation for some functionality?

Also, the _ErrorCodeProtocol has some mystery _ErrorType associatedtype requirement. I can't set the _ErrorType to POSIXError because POSIXError requires Self == POSIXError.Code apparently. If I set _ErrorType to NSError that doesn't satisfy the requirement either.

Is there something that I'm missing? Some undocumented requirement of the Error protocol in order to pattern match against it? I could implement static func ~= (lhs: Self, rhs: Error) operator myself, but I'm not sure exactly how I would go about doing that...


Update:
I was able to implement the pattern matching and it seems to be working:

extension ProjectNameError {
    public static func ~= (lhs: Self, rhs: Error) -> Bool {
        guard let selfError = rhs as? Self else { return false }
        return selfError == lhs
    }
}
Terms of Service

Privacy Policy

Cookie Policy