├── .github ├── FUNDING.yml └── workflows │ ├── checks.yml │ └── ci.yml ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE ├── Package.swift ├── Package@swift-5.5.swift ├── README.md ├── Sources └── LegibleError.swift ├── Tests ├── LegibleErrorTests │ ├── ClassTests.swift │ ├── Edgecases.swift │ ├── EnumTests.swift │ ├── StructTests.swift │ ├── XCTestManifests.swift │ └── etc.swift └── LinuxMain.swift └── tea.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mxcl 2 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | jobs: 6 | macOS: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - run: swift --version 11 | - run: swift test --parallel 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths: 4 | - Sources/** 5 | - Tests/** 6 | - .github/workflows/ci.yml 7 | schedule: 8 | - cron: '3 3 * * 2' # 3:03 AM, every Tuesday 9 | jobs: 10 | macOS: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | swift: 15 | - ~5.0 16 | - ~5.1 17 | - ~5.2 18 | - ~5.3 19 | os: 20 | - macos-10.15 21 | include: 22 | - swift: 5.4 23 | os: macos-11 24 | - swift: 5.5 25 | os: macos-11 26 | steps: 27 | - uses: actions/checkout@v2 28 | - uses: mxcl/xcodebuild@v1 29 | with: 30 | swift: ${{ matrix.swift }} 31 | warnings-as-errors: true 32 | - run: | 33 | swift test --generate-linuxmain 34 | git diff --exit-code 35 | if: ${{ matrix.swift == '5.3' }} 36 | linux: 37 | runs-on: ubuntu-latest 38 | strategy: 39 | matrix: 40 | swift: 41 | - swift:4.2 42 | - swift:5.0 43 | - swift:5.1 44 | - swift:5.2 45 | - swift:5.3 46 | - swift:5.4 47 | - swiftlang/swift:nightly-5.5 48 | container: 49 | image: ${{ matrix.swift }} 50 | steps: 51 | - uses: actions/checkout@v2 52 | - run: swift test --parallel -Xswiftc -warnings-as-errors 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.xcodeproj 2 | /.build 3 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unlicense (Public Domain) 2 | ============================ 3 | 4 | This is free and unencumbered software released into the public domain. 5 | 6 | Anyone is free to copy, modify, publish, use, compile, sell, or 7 | distribute this software, either in source code form or as a compiled 8 | binary, for any purpose, commercial or non-commercial, and by any 9 | means. 10 | 11 | In jurisdictions that recognize copyright laws, the author or authors 12 | of this software dedicate any and all copyright interest in the 13 | software to the public domain. We make this dedication for the benefit 14 | of the public at large and to the detriment of our heirs and 15 | successors. We intend this dedication to be an overt act of 16 | relinquishment in perpetuity of all present and future rights to this 17 | software under copyright law. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 23 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | For more information, please refer to <> 28 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | import PackageDescription 3 | 4 | let name = "LegibleError" 5 | 6 | let package = Package( 7 | name: name, 8 | products: [ 9 | .library(name: name, targets: [name]), 10 | ], 11 | targets: [ 12 | .target(name: name, path: "Sources"), 13 | .testTarget(name: "LegibleErrorTests", dependencies: [.init(stringLiteral: name)]), 14 | ], 15 | swiftLanguageVersions: [.v4, .v4_2, .version("5")] 16 | ) 17 | -------------------------------------------------------------------------------- /Package@swift-5.5.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let name = "LegibleError" 5 | 6 | let package = Package( 7 | name: name, 8 | products: [ 9 | .library(name: name, targets: [name]), 10 | ], 11 | targets: [ 12 | .target(name: name, path: "Sources"), 13 | .testTarget(name: "LegibleErrorTests", dependencies: [.init(stringLiteral: name)]), 14 | ] 15 | ) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## LegibleError ![badge-platforms][] ![badge-languages][] [![badge-ci][]][gha] [![badge-codecov][]][codecov] [![badge-version][]][cocoapods] 2 | 3 | LegibleError’s goal is to prevent you showing the user a string like this: 4 | 5 | > The operation couldn’t be completed. (ThirdPartyModule.(unknown context at 0xx10d6b4a44).SomeError error 0.) 6 | 7 | That string is the default `localizedDescription` for a Swift `Error`. Instead 8 | use LegibleError and you’ll get something more like this: 9 | 10 | > The operation couldn’t be completed. (ThirdPartyModule.SomeError.networkFailure(http: 503)) 11 | 12 | ## `Error.legibleLocalizedDescription` 13 | 14 | If you have an `Error` like this: 15 | 16 | ```swift 17 | enum SystemError: Error { 18 | case databaseFailure(internalCode: Int) 19 | } 20 | 21 | let error = SystemError.databaseFailure 22 | // ^^ obviously you’d get this from a callback or `catch` in the real-world 23 | 24 | let alert = UIAlertController(…) 25 | alert.message = error.localizedDescription 26 | present(alert) 27 | ``` 28 | 29 | The alert will show: 30 | 31 | > The operation couldn’t be completed. (MyModule.(unknown context at 0xx10d6b4a44).SystemError error 0.) 32 | 33 | But if we were to use `.legibleLocalizedDescription`: 34 | 35 | ```swift 36 | import LegibleError 37 | 38 | let alert = UIAlertController(…) 39 | alert.message = error.legibleLocalizedDescription 40 | present(alert) 41 | ``` 42 | 43 | The alert will show: 44 | 45 | > The operation couldn’t be completed. (SystemError.databaseFailure(internalCode: 34)) 46 | 47 | Still not great, but way more useful in a bug report. 48 | 49 | If you want a great message, implement `LocalizedError` this will make both 50 | `localizedDescription` **and** `legibleLocalizedDescription` return the string 51 | you specify: 52 | 53 | ```swift 54 | enum SystemError: LocalizedError { 55 | case databaseFailure 56 | 57 | var errorDescription: String? { 58 | switch self { 59 | case databaseFailure(let code): 60 | return "A serious database failure occurred. Contact support. (#\(code))" 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | The alert will show: 67 | 68 | > A serious database failure occurred. Contact support. (#34) 69 | 70 | --- 71 | 72 | LegibleError exists because: 73 | 74 | 1. You have no control over third parties and cannot force them to implement 75 | `LocalizedError` 76 | 2. Some Errors in your codebase are very unlikely and thus “localizing” them is 77 | not a good maintenance burden. 78 | 3. When logging errors you want the full information without any risk that the 79 | localized version has “fallen behind”, get the compiler to do the work, in 80 | such cases use `legibleDescription` (see the next section). 81 | 82 | ## Loggable Error Descriptions 83 | 84 | This: 85 | 86 | ```swift 87 | let msg = "There was an error (\(error))" 88 | ``` 89 | 90 | Will give you this: 91 | 92 | > There was an error (databaseFailure) 93 | 94 | Which loses the context of the enum’s type; use `legibleDescription`: 95 | 96 | ```swift 97 | let msg = "There was an error! \(error.legibleDescription)" 98 | ``` 99 | 100 | > There was an error (SystemError.databaseFailure(internalCode: 34)) 101 | 102 | `legibleDescription` is to `description` where `legibleLocalizedDescription` is 103 | to `localizedDescription`. `legibleDescription` is always appropriate for 104 | communicating to *you*, the developer, which error happened. Use it in logs and 105 | to supplement a good message for the user. 106 | 107 | ## Way better descriptions on Linux 108 | 109 | Linux is a little behind, usually you only get `The operation could not be 110 | completed` on Linux. We fully support Linux. 111 | 112 | # Sponsorship 113 | 114 | If you or your company depend on this project, please consider [sponsorship] so 115 | I have justification for maintenance . 116 | 117 | ## Installation 118 | 119 | SwiftPM: 120 | 121 | ```swift 122 | package.append(.package(url: "https://github.com/mxcl/LegibleError.git", from: "1.0.0")) 123 | ``` 124 | 125 | CocoaPods: 126 | 127 | ```ruby 128 | pod 'LegibleError', '~> 1.0' 129 | ``` 130 | 131 | Carthage: 132 | 133 | > Waiting on: [@Carthage#1945]. 134 | 135 | 136 | [badge-platforms]: https://img.shields.io/badge/platforms-macOS%20%7C%20Linux%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS-lightgrey.svg 137 | [badge-languages]: https://img.shields.io/badge/swift-4.2%20%7C%205.x-orange.svg 138 | [badge-codecov]: https://codecov.io/gh/mxcl/LegibleError/branch/master/graph/badge.svg 139 | [badge-version]: https://img.shields.io/cocoapods/v/LegibleError.svg?label=version 140 | [badge-ci]: https://github.com/mxcl/LegibleError/workflows/Checks/badge.svg 141 | 142 | [gha]: https://github.com/mxcl/LegibleError/actions 143 | [codecov]: https://codecov.io/gh/mxcl/LegibleError 144 | [cocoapods]: https://cocoapods.org/pods/LegibleError 145 | 146 | [`LocalizedError`]: https://developer.apple.com/documentation/foundation/localizederror 147 | [@Carthage#1945]: https://github.com/Carthage/Carthage/pull/1945 148 | [sponsorship]: https://github.com/sponsors/mxcl 149 | -------------------------------------------------------------------------------- /Sources/LegibleError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | #if os(Linux) 4 | #if swift(>=5.1) 5 | let theOperationCouldNotBeCompleted = "The operation could not be completed." 6 | #else 7 | let theOperationCouldNotBeCompleted = "The operation could not be completed" 8 | #endif 9 | #else 10 | let theOperationCouldNotBeCompleted = "The operation couldn’t be completed." 11 | #endif 12 | 13 | extension Error { 14 | /// - Returns: A fully qualified representation of this error. 15 | public var legibleDescription: String { 16 | switch errorType { 17 | case .swiftError(.enum?), .swiftLocalizedError(_, .enum?): 18 | return "\(type(of: self)).\(self)" 19 | case .swiftError(.class?), .swiftLocalizedError(_, .class?): 20 | //TODO better 21 | return "\(type(of: self))" 22 | case .swiftError, .swiftLocalizedError: 23 | return String(describing: self) 24 | case let .nsError(nsError, domain, code): 25 | if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError { 26 | return "\(domain)(\(code), \(underlyingError.domain)(\(underlyingError.code)))" 27 | } else { 28 | return "\(domain)(\(code))" 29 | } 30 | } 31 | } 32 | 33 | /// - Returns: A fully qualified, user-visible representation of this error. 34 | public var legibleLocalizedDescription: String { 35 | switch errorType { 36 | case .swiftError: 37 | return "\(theOperationCouldNotBeCompleted) (\(legibleDescription))" 38 | case .swiftLocalizedError(let msg, _): 39 | return msg 40 | case .nsError(_, "kCLErrorDomain", 0): 41 | return "The location could not be determined." 42 | // ^^ Apple don’t provide a localized description for this 43 | case let .nsError(nsError, domain, code): 44 | if !localizedDescription.hasPrefix(theOperationCouldNotBeCompleted) { 45 | return localizedDescription 46 | //FIXME ^^ for non-EN 47 | } else if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? Error { 48 | return underlyingError.legibleLocalizedDescription 49 | } else { 50 | // usually better than the localizedDescription, but not pretty 51 | return "\(theOperationCouldNotBeCompleted) (\(domain).\(code))" 52 | } 53 | } 54 | } 55 | 56 | private var errorType: ErrorType { 57 | #if os(Linux) 58 | // without the `as Any` noop this generates an (incorrect) warning 59 | let isNSError = self as Any is NSError 60 | #else 61 | let foo: Any = self 62 | let nativeClassNames = ["_SwiftNativeNSError", "__SwiftNativeNSError"] 63 | let selfClassName = String(cString: object_getClassName(self)) 64 | let isNSError = !nativeClassNames.contains(selfClassName) && foo is NSObject 65 | // ^^ ∵ otherwise implicit bridging implicitly casts as for other tests 66 | #endif 67 | 68 | if isNSError { 69 | let nserr = self as NSError 70 | return .nsError(nserr, domain: nserr.domain, code: nserr.code) 71 | } else if let err = self as? LocalizedError, let msg = err.errorDescription { 72 | return .swiftLocalizedError(msg, Mirror(reflecting: self).displayStyle) 73 | } else { 74 | return .swiftError(Mirror(reflecting: self).displayStyle) 75 | } 76 | } 77 | } 78 | 79 | private enum ErrorType { 80 | case nsError(NSError, domain: String, code: Int) 81 | case swiftLocalizedError(String, Mirror.DisplayStyle?) 82 | case swiftError(Mirror.DisplayStyle?) 83 | } 84 | -------------------------------------------------------------------------------- /Tests/LegibleErrorTests/ClassTests.swift: -------------------------------------------------------------------------------- 1 | @testable import LegibleError 2 | import XCTest 3 | 4 | public class PublicFoo: Error { 5 | let a = "a" 6 | } 7 | 8 | class InternalFoo: Error { 9 | let a = "a" 10 | } 11 | 12 | class PrivateFoo: Error { 13 | let a = "a" 14 | } 15 | 16 | class ClassTests: XCTestCase { 17 | func test_local_Swift_class() { 18 | 19 | //FIXME! 20 | 21 | class Foo: Error { 22 | let a = "a" 23 | } 24 | 25 | let foo = Foo() 26 | 27 | #if os(macOS) && swift(>=5.0) 28 | XCTAssertMatches( 29 | foo.legibleDescription, 30 | "^LegibleErrorTests\\.ClassTests\\.\\(unknown context at \\$.+\\)\\.\\(unknown context at \\$.+\\)\\.Foo\\(1\\)$") 31 | XCTAssertMatches( 32 | foo.legibleLocalizedDescription, 33 | "^\(theOperationCouldNotBeCompleted) \\(LegibleErrorTests\\.ClassTests\\.\\(unknown context at \\$.+\\)\\.\\(unknown context at \\$.+\\)\\.Foo\\.1\\)$") 34 | #else 35 | XCTAssertEqual(foo.legibleDescription, "Foo") 36 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (Foo)") 37 | #endif 38 | } 39 | 40 | func test_public_Swift_class() { 41 | let foo = PublicFoo() 42 | 43 | #if os(macOS) 44 | #if swift(>=5.0) 45 | XCTAssertEqual(foo.legibleDescription, "LegibleErrorTests.PublicFoo(1)") 46 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (LegibleErrorTests.PublicFoo.1)") 47 | #else 48 | XCTAssertEqual(foo.legibleDescription, "PublicFoo") 49 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PublicFoo)") 50 | #endif 51 | #else 52 | //FIXME 53 | XCTAssertEqual(foo.legibleDescription, "PublicFoo") 54 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PublicFoo)") 55 | #endif 56 | } 57 | 58 | func test_internal_Swift_class() { 59 | let foo = InternalFoo() 60 | 61 | #if os(macOS) 62 | #if swift(>=5.0) 63 | XCTAssertEqual(foo.legibleDescription, "LegibleErrorTests.InternalFoo(1)") 64 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (LegibleErrorTests.InternalFoo.1)") 65 | #else 66 | XCTAssertEqual(foo.legibleDescription, "InternalFoo") 67 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (InternalFoo)") 68 | #endif 69 | #else 70 | XCTAssertEqual(foo.legibleDescription, "InternalFoo") 71 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (InternalFoo)") 72 | #endif 73 | } 74 | 75 | func test_private_Swift_class() { 76 | let foo = PrivateFoo() 77 | 78 | #if os(macOS) 79 | #if swift(>=5.0) 80 | XCTAssertEqual(foo.legibleDescription, "LegibleErrorTests.PrivateFoo(1)") 81 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (LegibleErrorTests.PrivateFoo.1)") 82 | #else 83 | XCTAssertEqual(foo.legibleDescription, "PrivateFoo") 84 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PrivateFoo)") 85 | #endif 86 | #else 87 | XCTAssertEqual(foo.legibleDescription, "PrivateFoo") 88 | XCTAssertEqual(foo.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PrivateFoo)") 89 | #endif 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/LegibleErrorTests/Edgecases.swift: -------------------------------------------------------------------------------- 1 | @testable import LegibleError 2 | import XCTest 3 | 4 | class Edgcases: XCTestCase { 5 | func test_vanilla_NSError() { 6 | let Foo = { NSError(domain: "a", code: 1) } 7 | XCTAssertEqual(Foo().legibleDescription, "a(1)") 8 | XCTAssertEqual(Foo().legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (a.1)") 9 | #if !os(Linux) || swift(>=5.1) 10 | XCTAssertEqual(Foo().localizedDescription, "\(theOperationCouldNotBeCompleted) (a error 1.)") 11 | #else 12 | XCTAssertEqual(Foo().localizedDescription, theOperationCouldNotBeCompleted) 13 | #endif 14 | } 15 | 16 | func test_derived_NSError() { 17 | class Foo: NSError { 18 | convenience init() { 19 | self.init(domain: "a", code: 1) 20 | } 21 | } 22 | 23 | XCTAssertEqual(Foo().legibleDescription, "a(1)") 24 | XCTAssertEqual(Foo().legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (a.1)") 25 | #if !os(Linux) || swift(>=5.1) 26 | XCTAssertEqual(Foo().localizedDescription, "\(theOperationCouldNotBeCompleted) (a error 1.)") 27 | #else 28 | XCTAssertEqual(Foo().localizedDescription, theOperationCouldNotBeCompleted) 29 | #endif 30 | } 31 | 32 | func test_derived_annotated_NSError() { 33 | class Foo: NSError { 34 | convenience init() { 35 | self.init(domain: "a", code: 1, userInfo: [ 36 | NSLocalizedDescriptionKey: "Foobar" 37 | ]) 38 | } 39 | } 40 | 41 | XCTAssertEqual(Foo().legibleDescription, "a(1)") 42 | XCTAssertEqual(Foo().legibleLocalizedDescription, "Foobar") 43 | XCTAssertEqual(Foo().localizedDescription, "Foobar") 44 | } 45 | 46 | func test_derived_underlying_NSError() { 47 | class Foo: NSError { 48 | convenience init() { 49 | self.init(domain: "a", code: 1, userInfo: [ 50 | NSUnderlyingErrorKey: Foo(domain: "b", code: 2, userInfo: [ 51 | NSLocalizedDescriptionKey: "Foobar" 52 | ]) 53 | ]) 54 | } 55 | } 56 | 57 | XCTAssertEqual(Foo().legibleDescription, "a(1, b(2))") 58 | XCTAssertEqual(Foo().legibleLocalizedDescription, "Foobar") 59 | #if !os(Linux) || swift(>=5.1) 60 | XCTAssertEqual(Foo().localizedDescription, "\(theOperationCouldNotBeCompleted) (a error 1.)") 61 | #else 62 | XCTAssertEqual(Foo().localizedDescription, theOperationCouldNotBeCompleted) 63 | #endif 64 | } 65 | 66 | func test_CocoaError() { 67 | let err = CocoaError.error(.coderInvalidValue) 68 | let msg = "The data couldn’t be written because it isn’t in the correct format." 69 | 70 | XCTAssertEqual(err.legibleDescription, "NSCocoaErrorDomain(4866)") 71 | #if !os(Linux) 72 | XCTAssertEqual(err.legibleLocalizedDescription, msg) 73 | XCTAssertEqual(err.localizedDescription, msg) 74 | #elseif swift(>=5.1) 75 | XCTAssertEqual(err.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (NSCocoaErrorDomain.4866)") 76 | XCTAssertEqual(err.localizedDescription, "\(theOperationCouldNotBeCompleted) The data isn’t in the correct format.") 77 | #else 78 | XCTAssertEqual(err.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (NSCocoaErrorDomain.4866)") 79 | XCTAssertEqual(err.localizedDescription, theOperationCouldNotBeCompleted) 80 | #endif 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/LegibleErrorTests/EnumTests.swift: -------------------------------------------------------------------------------- 1 | @testable import LegibleError 2 | import XCTest 3 | 4 | public enum PublicEnum: Error { 5 | case a 6 | } 7 | 8 | enum InternalEnum: Error { 9 | case a 10 | } 11 | 12 | private enum PrivateEnum: Error { 13 | case a 14 | } 15 | 16 | class EnumTests: XCTestCase { 17 | func test_public_Swift_enum() { 18 | XCTAssertEqual(PublicEnum.a.legibleDescription, "PublicEnum.a") 19 | XCTAssertEqual(PublicEnum.a.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PublicEnum.a)") 20 | } 21 | 22 | func test_internal_Swift_enum() { 23 | XCTAssertEqual(InternalEnum.a.legibleDescription, "InternalEnum.a") 24 | XCTAssertEqual(InternalEnum.a.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (InternalEnum.a)") 25 | } 26 | 27 | func test_private_Swift_enum() { 28 | XCTAssertEqual(PrivateEnum.a.legibleDescription, "PrivateEnum.a") 29 | XCTAssertEqual(PrivateEnum.a.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PrivateEnum.a)") 30 | } 31 | 32 | func test_local_Swift_enum() { 33 | enum Enum: Error { 34 | case a 35 | } 36 | XCTAssertEqual(Enum.a.legibleDescription, "Enum.a") 37 | XCTAssertEqual(Enum.a.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (Enum.a)") 38 | } 39 | 40 | func test_Swift_LocalizedError_enum_without_errorDescription() { 41 | enum Foo: LocalizedError { 42 | case a 43 | } 44 | 45 | XCTAssertEqual(Foo.a.legibleDescription, "Foo.a") 46 | XCTAssertEqual(Foo.a.legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (Foo.a)") 47 | } 48 | 49 | func test_Swift_LocalizedError_enum_with_errorDescription() { 50 | enum Foo: LocalizedError { 51 | case a 52 | 53 | var errorDescription: String? { 54 | return "Foobar" 55 | } 56 | } 57 | 58 | XCTAssertEqual(Foo.a.legibleLocalizedDescription, "Foobar") 59 | XCTAssertEqual(Foo.a.legibleDescription, "Foo.a") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/LegibleErrorTests/StructTests.swift: -------------------------------------------------------------------------------- 1 | @testable import LegibleError 2 | import XCTest 3 | 4 | public struct PublicStruct: Error { 5 | let a = "a" 6 | } 7 | 8 | struct InternalStruct: Error { 9 | let a = "a" 10 | } 11 | 12 | private struct PrivateStruct: Error { 13 | let a = "a" 14 | } 15 | 16 | 17 | class StructTest: XCTestCase { 18 | func test_public_Swift_struct() { 19 | XCTAssertEqual(PublicStruct().legibleDescription, "PublicStruct(a: \"a\")") 20 | XCTAssertEqual(PublicStruct().legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PublicStruct(a: \"a\"))") 21 | } 22 | 23 | func test_internal_Swift_struct() { 24 | XCTAssertEqual(InternalStruct().legibleDescription, "InternalStruct(a: \"a\")") 25 | XCTAssertEqual(InternalStruct().legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (InternalStruct(a: \"a\"))") 26 | } 27 | 28 | func test_private_Swift_enum() { 29 | XCTAssertEqual(PrivateStruct().legibleDescription, "PrivateStruct(a: \"a\")") 30 | XCTAssertEqual(PrivateStruct().legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (PrivateStruct(a: \"a\"))") 31 | } 32 | 33 | func test_local_Swift_struct() { 34 | struct Struct: Error { 35 | let a = "a" 36 | } 37 | 38 | XCTAssertEqual(Struct().legibleDescription, "Struct(a: \"a\")") 39 | XCTAssertEqual(Struct().legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (Struct(a: \"a\"))") 40 | } 41 | 42 | func test_Swift_LocalizedError_struct_without_errorDescription() { 43 | struct Foo: LocalizedError { 44 | let a = "a" 45 | } 46 | 47 | XCTAssertEqual(Foo().legibleDescription, "Foo(a: \"a\")") 48 | XCTAssertEqual(Foo().legibleLocalizedDescription, "\(theOperationCouldNotBeCompleted) (Foo(a: \"a\"))") 49 | } 50 | 51 | func test_Swift_LocalizedError_struct_with_errorDescription() { 52 | struct Foo: LocalizedError { 53 | let a = "a" 54 | 55 | var errorDescription: String? { 56 | return "Foobar" 57 | } 58 | } 59 | 60 | XCTAssertEqual(Foo().legibleDescription, "Foo(a: \"a\")") 61 | XCTAssertEqual(Foo().legibleLocalizedDescription, "Foobar") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/LegibleErrorTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | #if !canImport(ObjectiveC) 2 | import XCTest 3 | 4 | extension ClassTests { 5 | // DO NOT MODIFY: This is autogenerated, use: 6 | // `swift test --generate-linuxmain` 7 | // to regenerate. 8 | static let __allTests__ClassTests = [ 9 | ("test_internal_Swift_class", test_internal_Swift_class), 10 | ("test_local_Swift_class", test_local_Swift_class), 11 | ("test_private_Swift_class", test_private_Swift_class), 12 | ("test_public_Swift_class", test_public_Swift_class), 13 | ] 14 | } 15 | 16 | extension Edgcases { 17 | // DO NOT MODIFY: This is autogenerated, use: 18 | // `swift test --generate-linuxmain` 19 | // to regenerate. 20 | static let __allTests__Edgcases = [ 21 | ("test_CocoaError", test_CocoaError), 22 | ("test_derived_annotated_NSError", test_derived_annotated_NSError), 23 | ("test_derived_NSError", test_derived_NSError), 24 | ("test_derived_underlying_NSError", test_derived_underlying_NSError), 25 | ("test_vanilla_NSError", test_vanilla_NSError), 26 | ] 27 | } 28 | 29 | extension EnumTests { 30 | // DO NOT MODIFY: This is autogenerated, use: 31 | // `swift test --generate-linuxmain` 32 | // to regenerate. 33 | static let __allTests__EnumTests = [ 34 | ("test_internal_Swift_enum", test_internal_Swift_enum), 35 | ("test_local_Swift_enum", test_local_Swift_enum), 36 | ("test_private_Swift_enum", test_private_Swift_enum), 37 | ("test_public_Swift_enum", test_public_Swift_enum), 38 | ("test_Swift_LocalizedError_enum_with_errorDescription", test_Swift_LocalizedError_enum_with_errorDescription), 39 | ("test_Swift_LocalizedError_enum_without_errorDescription", test_Swift_LocalizedError_enum_without_errorDescription), 40 | ] 41 | } 42 | 43 | extension StructTest { 44 | // DO NOT MODIFY: This is autogenerated, use: 45 | // `swift test --generate-linuxmain` 46 | // to regenerate. 47 | static let __allTests__StructTest = [ 48 | ("test_internal_Swift_struct", test_internal_Swift_struct), 49 | ("test_local_Swift_struct", test_local_Swift_struct), 50 | ("test_private_Swift_enum", test_private_Swift_enum), 51 | ("test_public_Swift_struct", test_public_Swift_struct), 52 | ("test_Swift_LocalizedError_struct_with_errorDescription", test_Swift_LocalizedError_struct_with_errorDescription), 53 | ("test_Swift_LocalizedError_struct_without_errorDescription", test_Swift_LocalizedError_struct_without_errorDescription), 54 | ] 55 | } 56 | 57 | public func __allTests() -> [XCTestCaseEntry] { 58 | return [ 59 | testCase(ClassTests.__allTests__ClassTests), 60 | testCase(Edgcases.__allTests__Edgcases), 61 | testCase(EnumTests.__allTests__EnumTests), 62 | testCase(StructTest.__allTests__StructTest), 63 | ] 64 | } 65 | #endif 66 | -------------------------------------------------------------------------------- /Tests/LegibleErrorTests/etc.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCTest 3 | 4 | func XCTAssertMatches(_ input: String, _ pattern: String, file: StaticString = #file, line: UInt = #line) { 5 | do { 6 | let rx = try NSRegularExpression(pattern: pattern) 7 | let range = NSRange(location: 0, length: input.utf16.count) 8 | if rx.firstMatch(in: input, range: range) == nil { 9 | XCTFail("\(input) did not match the provided pattern", file: file, line: line) 10 | } 11 | } catch { 12 | XCTFail("Pattern failed to compile", file: file, line: line) 13 | } 14 | } 15 | 16 | #if !os(macOS) && !os(Linux) 17 | import XCTest 18 | 19 | // SwiftPM generates code that is improperly escaped thus we require this to 20 | // compile on iOS & tvOS. 21 | public typealias XCTestCaseEntry = (testCaseClass: XCTestCase.Type, allTests: [(String, (XCTestCase) throws -> Void)]) 22 | 23 | public func testCase(_ allTests: [(String, (T) -> () throws -> Void)]) -> XCTestCaseEntry { 24 | fatalError() 25 | } 26 | 27 | public func testCase(_ allTests: [(String, (T) -> () -> Void)]) -> XCTestCaseEntry { 28 | fatalError() 29 | } 30 | #endif 31 | 32 | #if swift(>=5) 33 | let addressPattern = "0x.?+" 34 | #else 35 | let addressPattern = "\\$.?+" 36 | #endif 37 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import LegibleErrorTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += LegibleErrorTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | # created with https://mash.pkgx.sh/mxcl/tea-register 3 | --- 4 | version: 1.0.0 5 | codeOwners: 6 | - '0x5E2DE4A68df811AAAD32d71fb065e6946fA5C8d9' # mxcl 7 | quorum: 1 8 | --------------------------------------------------------------------------------