Skip to content

Commit ca396ce

Browse files
authored
Extract localized error messages from the toolkit (#477)
1 parent d0de224 commit ca396ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+479
-534
lines changed

Documentation/Migration Guide.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All migration steps necessary in reading apps to upgrade to major versions of th
44

55
## Unreleased
66

7+
### Error management
8+
9+
The error hierarchy returned by the Readium APIs has been revamped and simplified. They are no longer `LocalizedError` instances, so you must provide your own user-friendly error messages. Refer to the `Readium.swift` file in the Test App for an example.
10+
711
### Opening a `Publication`
812

913
The `Streamer` object has been deprecated in favor of components with smaller responsibilities:

Sources/LCP/Content Protection/LCPContentProtection.swift

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,6 @@ final class LCPContentProtection: ContentProtection, Loggable {
6565
}
6666
}
6767

68-
private extension Publication.OpeningError {
69-
static func wrap(_ error: LCPError) -> Publication.OpeningError {
70-
switch error {
71-
case .licenseIsBusy, .network, .licenseContainer:
72-
return .unavailable(error)
73-
case .licenseStatus:
74-
return .forbidden(error)
75-
default:
76-
return .parsingFailed(error)
77-
}
78-
}
79-
}
80-
8168
private final class LCPContentProtectionService: ContentProtectionService {
8269
let license: LCPLicense?
8370
let error: Error?

Sources/LCP/LCPError.swift

Lines changed: 21 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import Foundation
88
import ReadiumShared
99

10-
public enum LCPError: LocalizedError {
10+
public enum LCPError: Error {
1111
/// The license could not be retrieved because the passphrase is unknown.
1212
case missingPassphrase
1313

@@ -52,138 +52,51 @@ public enum LCPError: LocalizedError {
5252

5353
/// An unknown low-level error was reported.
5454
case unknown(Error?)
55-
56-
public var errorDescription: String? {
57-
switch self {
58-
case .missingPassphrase: return nil
59-
case .notALicenseDocument: return nil
60-
case .licenseIsBusy:
61-
return ReadiumLCPLocalizedString("LCPError.licenseIsBusy")
62-
case let .licenseIntegrity(error):
63-
let description: String = {
64-
switch error {
65-
case .licenseOutOfDate:
66-
return ReadiumLCPLocalizedString("LCPClientError.licenseOutOfDate")
67-
case .certificateRevoked:
68-
return ReadiumLCPLocalizedString("LCPClientError.certificateRevoked")
69-
case .certificateSignatureInvalid:
70-
return ReadiumLCPLocalizedString("LCPClientError.certificateSignatureInvalid")
71-
case .licenseSignatureDateInvalid:
72-
return ReadiumLCPLocalizedString("LCPClientError.licenseSignatureDateInvalid")
73-
case .licenseSignatureInvalid:
74-
return ReadiumLCPLocalizedString("LCPClientError.licenseSignatureInvalid")
75-
case .contextInvalid:
76-
return ReadiumLCPLocalizedString("LCPClientError.contextInvalid")
77-
case .contentKeyDecryptError:
78-
return ReadiumLCPLocalizedString("LCPClientError.contentKeyDecryptError")
79-
case .userKeyCheckInvalid:
80-
return ReadiumLCPLocalizedString("LCPClientError.userKeyCheckInvalid")
81-
case .contentDecryptError:
82-
return ReadiumLCPLocalizedString("LCPClientError.contentDecryptError")
83-
case .unknown:
84-
return ReadiumLCPLocalizedString("LCPClientError.unknown")
85-
}
86-
}()
87-
return ReadiumLCPLocalizedString("LCPError.licenseIntegrity", description)
88-
case let .licenseStatus(error):
89-
return error.localizedDescription
90-
case .licenseContainer:
91-
return ReadiumLCPLocalizedString("LCPError.licenseContainer")
92-
case .licenseInteractionNotAvailable:
93-
return ReadiumLCPLocalizedString("LCPError.licenseInteractionNotAvailable")
94-
case .licenseProfileNotSupported:
95-
return ReadiumLCPLocalizedString("LCPError.licenseProfileNotSupported")
96-
case .crlFetching:
97-
return ReadiumLCPLocalizedString("LCPError.crlFetching")
98-
case let .licenseRenew(error):
99-
return error.localizedDescription
100-
case let .licenseReturn(error):
101-
return error.localizedDescription
102-
case .parsing:
103-
return ReadiumLCPLocalizedString("LCPError.parsing")
104-
case let .network(error):
105-
return error?.localizedDescription ?? ReadiumLCPLocalizedString("LCPError.network")
106-
case let .runtime(error):
107-
return error
108-
case let .unknown(error):
109-
return error?.localizedDescription
110-
}
111-
}
11255
}
11356

11457
/// Errors while checking the status of the License, using the Status Document.
115-
public enum StatusError: LocalizedError {
116-
// For the case (revoked, returned, cancelled, expired), app should notify the user and stop there. The message to the user must be clear about the status of the license: don't display "expired" if the status is "revoked". The date and time corresponding to the new status should be displayed (e.g. "The license expired on 01 January 2018").
58+
///
59+
/// For the case (revoked, returned, cancelled, expired), app should notify the
60+
/// user and stop there. The message to the user must be clear about the status
61+
/// of the license: don't display "expired" if the status is "revoked". The
62+
/// date and time corresponding to the new status should be displayed (e.g.
63+
/// "The license expired on 01 January 2018").
64+
///
65+
/// If the license has been revoked, the user message should display the number
66+
/// of devices which registered to the server. This count can be calculated
67+
/// from the number of "register" events in the status document. If no event is
68+
/// logged in the status document, no such message should appear (certainly not
69+
/// "The license was registered by 0 devices").
70+
public enum StatusError: Error {
71+
/// This license was cancelled on the given date.
11772
case cancelled(Date)
73+
/// This license has been returned on the given date.
11874
case returned(Date)
75+
/// This license started and expired on the given dates.
11976
case expired(start: Date, end: Date)
120-
// If the license has been revoked, the user message should display the number of devices which registered to the server. This count can be calculated from the number of "register" events in the status document. If no event is logged in the status document, no such message should appear (certainly not "The license was registered by 0 devices").
77+
/// This license was revoked on the given date, after being activated on
78+
/// `devicesCount` devices.
12179
case revoked(Date, devicesCount: Int)
122-
123-
public var errorDescription: String? {
124-
let dateFormatter = DateFormatter()
125-
dateFormatter.dateStyle = .medium
126-
127-
switch self {
128-
case let .cancelled(date):
129-
return ReadiumLCPLocalizedString("StatusError.cancelled", dateFormatter.string(from: date))
130-
131-
case let .returned(date):
132-
return ReadiumLCPLocalizedString("StatusError.returned", dateFormatter.string(from: date))
133-
134-
case let .expired(start: start, end: end):
135-
if start > Date() {
136-
return ReadiumLCPLocalizedString("StatusError.expired.start", dateFormatter.string(from: start))
137-
} else {
138-
return ReadiumLCPLocalizedString("StatusError.expired.end", dateFormatter.string(from: end))
139-
}
140-
141-
case let .revoked(date, devicesCount):
142-
return ReadiumLCPLocalizedString("StatusError.revoked", dateFormatter.string(from: date), devicesCount)
143-
}
144-
}
14580
}
14681

14782
/// Errors while renewing a loan.
148-
public enum RenewError: LocalizedError {
83+
public enum RenewError: Error {
14984
// Your publication could not be renewed properly.
15085
case renewFailed
15186
// Incorrect renewal period, your publication could not be renewed.
15287
case invalidRenewalPeriod(maxRenewDate: Date?)
15388
// An unexpected error has occurred on the licensing server.
15489
case unexpectedServerError
155-
156-
public var errorDescription: String? {
157-
switch self {
158-
case .renewFailed:
159-
return ReadiumLCPLocalizedString("RenewError.renewFailed")
160-
case .invalidRenewalPeriod(maxRenewDate: _):
161-
return ReadiumLCPLocalizedString("RenewError.invalidRenewalPeriod")
162-
case .unexpectedServerError:
163-
return ReadiumLCPLocalizedString("RenewError.unexpectedServerError")
164-
}
165-
}
16690
}
16791

16892
/// Errors while returning a loan.
169-
public enum ReturnError: LocalizedError {
93+
public enum ReturnError: Error {
17094
// Your publication could not be returned properly.
17195
case returnFailed
17296
// Your publication has already been returned before or is expired.
17397
case alreadyReturnedOrExpired
17498
// An unexpected error has occurred on the licensing server.
17599
case unexpectedServerError
176-
177-
public var errorDescription: String? {
178-
switch self {
179-
case .returnFailed:
180-
return ReadiumLCPLocalizedString("ReturnError.returnFailed")
181-
case .alreadyReturnedOrExpired:
182-
return ReadiumLCPLocalizedString("ReturnError.alreadyReturnedOrExpired")
183-
case .unexpectedServerError:
184-
return ReadiumLCPLocalizedString("ReturnError.unexpectedServerError")
185-
}
186-
}
187100
}
188101

189102
/// Errors while parsing the License or Status JSON Documents.

Sources/LCP/Resources/en.lproj/Localizable.strings

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,41 +30,3 @@
3030
"ReadiumLCP.dialog.support.phone" = "Phone";
3131
"ReadiumLCP.dialog.support.mail" = "Mail";
3232

33-
/* LCPError: General error messages */
34-
"ReadiumLCP.LCPError.licenseIsBusy" = "Can't perform this operation at the moment.";
35-
"ReadiumLCP.LCPError.licenseIntegrity" = "License integrity: %@";
36-
"ReadiumLCP.LCPError.licenseContainer" = "Can't access the License Document.";
37-
"ReadiumLCP.LCPError.licenseInteractionNotAvailable" = "This interaction is not available.";
38-
"ReadiumLCP.LCPError.licenseProfileNotSupported" = "This License has a profile identifier that this app cannot handle, the publication cannot be processed.";
39-
"ReadiumLCP.LCPError.crlFetching" = "Can't retrieve the Certificate Revocation List.";
40-
"ReadiumLCP.LCPError.parsing" = "Failed to parse the License Document.";
41-
"ReadiumLCP.LCPError.network" = "Network error.";
42-
43-
/* LCPClientError: Errors while checking the integrity of the License */
44-
"ReadiumLCP.LCPClientError.licenseOutOfDate" = "License is out of date (check start and end date).";
45-
"ReadiumLCP.LCPClientError.certificateRevoked" = "Certificate has been revoked in the CRL.";
46-
"ReadiumLCP.LCPClientError.certificateSignatureInvalid" = "Certificate has not been signed by CA.";
47-
"ReadiumLCP.LCPClientError.licenseSignatureDateInvalid" = "License has been issued by an expired certificate.";
48-
"ReadiumLCP.LCPClientError.licenseSignatureInvalid" = "License signature does not match.";
49-
"ReadiumLCP.LCPClientError.contextInvalid" = "The DRM context is invalid.";
50-
"ReadiumLCP.LCPClientError.contentKeyDecryptError" = "Unable to decrypt encrypted content key from user key.";
51-
"ReadiumLCP.LCPClientError.userKeyCheckInvalid" = "User key check invalid.";
52-
"ReadiumLCP.LCPClientError.contentDecryptError" = "Unable to decrypt encrypted content from content key.";
53-
"ReadiumLCP.LCPClientError.unknown" = "Unknown error.";
54-
55-
/* StatusError: Errors while checking the status of the License, using the Status Document. */
56-
"ReadiumLCP.StatusError.cancelled" = "This license was cancelled on %@.";
57-
"ReadiumLCP.StatusError.returned" = "This license has been returned on %@.";
58-
"ReadiumLCP.StatusError.expired.start" = "This license starts on %@.";
59-
"ReadiumLCP.StatusError.expired.end" = "This license expired on %@.";
60-
"ReadiumLCP.StatusError.revoked" = "This license was revoked by its provider on %1$@.\nIt was registered by %2$d device(s)";
61-
62-
/* RenewError: Errors while renewing a loan. */
63-
"ReadiumLCP.RenewError.renewFailed" = "Your publication could not be renewed properly.";
64-
"ReadiumLCP.RenewError.invalidRenewalPeriod" = "Incorrect renewal period, your publication could not be renewed.";
65-
"ReadiumLCP.RenewError.unexpectedServerError" = "An unexpected error has occurred on the server.";
66-
67-
/* ReturnError: Errors while returning a loan. */
68-
"ReadiumLCP.ReturnError.returnFailed" = "Your publication could not be returned properly.";
69-
"ReadiumLCP.ReturnError.alreadyReturnedOrExpired" = "Your publication has already been returned before or is expired.";
70-
"ReadiumLCP.ReturnError.unexpectedServerError" = "An unexpected error has occurred on the server.";

Sources/LCP/Toolkit/Deferred.swift

Lines changed: 0 additions & 23 deletions
This file was deleted.

Sources/Navigator/Audiobook/PublicationMediaLoader.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,9 @@ import ReadiumShared
1313
///
1414
/// Useful for local resources or when you need to customize the way HTTP requests are sent.
1515
final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate, Loggable {
16-
public enum AssetError: LocalizedError {
16+
public enum AssetError: Error {
17+
/// Can't produce an URL to create an AVAsset for the given HREF.
1718
case invalidHREF(String)
18-
19-
public var errorDescription: String? {
20-
switch self {
21-
case let .invalidHREF(href):
22-
return "Can't produce an URL to create an AVAsset for HREF \(href)"
23-
}
24-
}
2519
}
2620

2721
private let publication: Publication

Sources/Navigator/Navigator.swift

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,7 @@ public extension NavigatorDelegate {
157157
func navigator(_ navigator: Navigator, didFailToLoadResourceAt href: String, withError error: ReadError) {}
158158
}
159159

160-
public enum NavigatorError: LocalizedError {
160+
public enum NavigatorError: Error {
161161
/// The user tried to copy the text selection but the DRM License doesn't allow it.
162162
case copyForbidden
163-
164-
public var errorDescription: String? {
165-
switch self {
166-
case .copyForbidden:
167-
return ReadiumNavigatorLocalizedString("NavigatorError.copyForbidden")
168-
}
169-
}
170163
}

Sources/Navigator/Resources/en.lproj/Localizable.strings

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44
available in the top-level LICENSE file of the project.
55
*/
66

7-
"ReadiumNavigator.NavigatorError.copyForbidden" = "You exceeded the amount of characters allowed to be copied.";
87
"ReadiumNavigator.EditingAction.share" = "Share…";

Sources/Shared/Publication/Protection/ContentProtection.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,9 @@ import Foundation
1414
public protocol ContentProtection {
1515
/// Attempts to unlock a potentially protected publication asset.
1616
///
17-
/// The Streamer will create a leaf `fetcher` for the low-level `asset` access (e.g.
18-
/// `ArchiveFetcher` for a ZIP archive), to avoid having each Content Protection open the asset
19-
/// to check if it's protected or not.
20-
///
21-
/// A publication might be protected in such a way that the asset format can't be recognized,
22-
/// in which case the Content Protection will have the responsibility of creating a new leaf
23-
/// `Fetcher`.
24-
///
25-
/// - Returns: A `ProtectedAsset` in case of success, nil if the asset is not protected by this
26-
/// technology or a `Publication.OpeningError` if the asset can't be successfully opened, even
27-
/// in restricted mode.
17+
/// - Returns: An ``Asset`` in case of success or an
18+
/// ``ContentProtectionOpenError`` if the asset can't be successfully
19+
/// opened even in restricted mode.
2820
func open(
2921
asset: Asset,
3022
credentials: String?,

Sources/Shared/Publication/Publication.swift

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ public class Publication: Closeable, Loggable {
169169
}
170170

171171
/// Errors occurring while opening a Publication.
172-
public enum OpeningError: LocalizedError {
172+
@available(*, unavailable, message: "Not used anymore")
173+
public enum OpeningError: Error {
173174
/// The file format could not be recognized by any parser.
174175
case unsupportedFormat
175176
/// The publication file was not found on the file system.
@@ -184,23 +185,6 @@ public class Publication: Closeable, Loggable {
184185
/// The provided credentials are incorrect and we can't open the publication in a
185186
/// `restricted` state (e.g. for a password-protected ZIP).
186187
case incorrectCredentials
187-
188-
public var errorDescription: String? {
189-
switch self {
190-
case .unsupportedFormat:
191-
return ReadiumSharedLocalizedString("Publication.OpeningError.unsupportedFormat")
192-
case .notFound:
193-
return ReadiumSharedLocalizedString("Publication.OpeningError.notFound")
194-
case .parsingFailed:
195-
return ReadiumSharedLocalizedString("Publication.OpeningError.parsingFailed")
196-
case .forbidden:
197-
return ReadiumSharedLocalizedString("Publication.OpeningError.forbidden")
198-
case .unavailable:
199-
return ReadiumSharedLocalizedString("Publication.OpeningError.unavailable")
200-
case .incorrectCredentials:
201-
return ReadiumSharedLocalizedString("Publication.OpeningError.incorrectCredentials")
202-
}
203-
}
204188
}
205189

206190
/// Holds the components of a `Publication` to build it.

Sources/Shared/Publication/Services/Search/SearchService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ public struct SearchOptions: Hashable {
122122
public typealias SearchResult<Success> = Result<Success, SearchError>
123123

124124
/// Represents an error which might occur during a search activity.
125-
public enum SearchError: LocalizedError {
125+
public enum SearchError: Error {
126126
/// The publication is not searchable.
127127
case publicationNotSearchable
128128

129129
/// The provided search query cannot be handled by the service.
130-
case badQuery(LocalizedError)
130+
case badQuery(Error)
131131

132132
/// An error occurred while accessing one of the publication's resources.
133133
case reading(ReadError)

0 commit comments

Comments
 (0)