Create CommonErrors module.

This commit is contained in:
Brent Simmons 2024-04-06 17:46:17 -07:00
parent fbc0c72cd5
commit 2eb14ada1f
12 changed files with 285 additions and 143 deletions

View File

@ -20,7 +20,8 @@ let package = Package(
.package(path: "../SyncDatabase"),
.package(path: "../Core"),
.package(path: "../CloudKitExtras"),
.package(path: "../ReaderAPI")
.package(path: "../ReaderAPI"),
.package(path: "../CommonErrors")
],
targets: [
.target(
@ -35,7 +36,8 @@ let package = Package(
"Database",
"Core",
"CloudKitExtras",
"ReaderAPI"
"ReaderAPI",
"CommonErrors"
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")

View File

@ -8,13 +8,11 @@
import Foundation
import Web
import CommonErrors
public enum AccountError: LocalizedError {
case createErrorNotFound
case createErrorAlreadySubscribed
case opmlImportInProgress
case wrappedError(error: Error, accountID: String, accountName: String)
typealias AccountError = CommonError // Temporary, for compatibility with existing code
public extension CommonError {
@MainActor public var account: Account? {
if case .wrappedError(_, let accountID, _) = self {
@ -23,78 +21,8 @@ public enum AccountError: LocalizedError {
return nil
}
}
@MainActor public static func wrappedError(error: Error, account: Account) -> AccountError {
wrappedError(error: error, accountID: account.accountID, accountName: account.nameForDisplay)
}
@MainActor public var isCredentialsError: Bool {
if case .wrappedError(let error, _, _) = self {
if case TransportError.httpError(let status) = error {
return isCredentialsError(status: status)
}
}
return false
}
public var errorDescription: String? {
switch self {
case .createErrorNotFound:
return NSLocalizedString("The feed couldnt be found and cant be added.", comment: "Not found")
case .createErrorAlreadySubscribed:
return NSLocalizedString("You are already subscribed to this feed and cant add it again.", comment: "Already subscribed")
case .opmlImportInProgress:
return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running")
case .wrappedError(let error, _, let accountName):
switch error {
case TransportError.httpError(let status):
if isCredentialsError(status: status) {
let localizedText = NSLocalizedString("Your “%@” credentials are invalid or expired.", comment: "Invalid or expired")
return NSString.localizedStringWithFormat(localizedText as NSString, accountName) as String
} else {
return unknownError(error, accountName)
}
default:
return unknownError(error, accountName)
}
}
}
public var recoverySuggestion: String? {
switch self {
case .createErrorNotFound:
return nil
case .createErrorAlreadySubscribed:
return nil
case .wrappedError(let error, _, _):
switch error {
case TransportError.httpError(let status):
if isCredentialsError(status: status) {
return NSLocalizedString("Please update your credentials for this account, or ensure that your account with this service is still valid.", comment: "Expired credentials")
} else {
return NSLocalizedString("Please try again later.", comment: "Try later")
}
default:
return NSLocalizedString("Please try again later.", comment: "Try later")
}
default:
return NSLocalizedString("Please try again later.", comment: "Try later")
}
}
}
// MARK: Private
private extension AccountError {
func unknownError(_ error: Error, _ accountName: String) -> String {
let localizedText = NSLocalizedString("An error occurred while processing the “%@” account: %@", comment: "Unknown error")
return NSString.localizedStringWithFormat(localizedText as NSString, accountName, error.localizedDescription) as String
}
func isCredentialsError(status: Int) -> Bool {
return status == 401 || status == 403
}
}

View File

@ -16,26 +16,6 @@ import Database
import Core
import ReaderAPI
public enum ReaderAPIAccountDelegateError: LocalizedError {
case unknown
case invalidParameter
case invalidResponse
case urlNotFound
public var errorDescription: String? {
switch self {
case .unknown:
return NSLocalizedString("An unexpected error occurred.", comment: "An unexpected error occurred.")
case .invalidParameter:
return NSLocalizedString("An invalid parameter was passed.", comment: "An invalid parameter was passed.")
case .invalidResponse:
return NSLocalizedString("There was an invalid response from the server.", comment: "There was an invalid response from the server.")
case .urlNotFound:
return NSLocalizedString("The API URL wasn't found.", comment: "The API URL wasn't found.")
}
}
}
final class ReaderAPIAccountDelegate: AccountDelegate {
private let variant: ReaderAPIVariant

8
CommonErrors/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,30 @@
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "CommonErrors",
platforms: [.macOS(.v14), .iOS(.v17)],
products: [
.library(
name: "CommonErrors",
targets: ["CommonErrors"]),
],
dependencies: [
.package(path: "../Web"),
],
targets: [
.target(
name: "CommonErrors",
dependencies: [
"Web"
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
),
.testTarget(
name: "CommonErrorsTests",
dependencies: ["CommonErrors"]),
]
)

View File

@ -0,0 +1,86 @@
//
// CommonError.swift
//
//
// Created by Brent Simmons on 4/6/24.
//
import Foundation
import Web
public enum CommonError: LocalizedError {
case createErrorNotFound
case createErrorAlreadySubscribed
case opmlImportInProgress
case wrappedError(error: Error, accountID: String, accountName: String)
@MainActor public var isCredentialsError: Bool {
if case .wrappedError(let error, _, _) = self {
if case TransportError.httpError(let status) = error {
return isCredentialsError(status: status)
}
}
return false
}
public var errorDescription: String? {
switch self {
case .createErrorNotFound:
return NSLocalizedString("The feed couldnt be found and cant be added.", comment: "Not found")
case .createErrorAlreadySubscribed:
return NSLocalizedString("You are already subscribed to this feed and cant add it again.", comment: "Already subscribed")
case .opmlImportInProgress:
return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running")
case .wrappedError(let error, _, let accountName):
switch error {
case TransportError.httpError(let status):
if isCredentialsError(status: status) {
let localizedText = NSLocalizedString("Your “%@” credentials are invalid or expired.", comment: "Invalid or expired")
return NSString.localizedStringWithFormat(localizedText as NSString, accountName) as String
} else {
return unknownError(error, accountName)
}
default:
return unknownError(error, accountName)
}
}
}
public var recoverySuggestion: String? {
switch self {
case .createErrorNotFound:
return nil
case .createErrorAlreadySubscribed:
return nil
case .wrappedError(let error, _, _):
switch error {
case TransportError.httpError(let status):
if isCredentialsError(status: status) {
return NSLocalizedString("Please update your credentials for this account, or ensure that your account with this service is still valid.", comment: "Expired credentials")
} else {
return NSLocalizedString("Please try again later.", comment: "Try later")
}
default:
return NSLocalizedString("Please try again later.", comment: "Try later")
}
default:
return NSLocalizedString("Please try again later.", comment: "Try later")
}
}
}
// MARK: Private
private extension CommonError {
func unknownError(_ error: Error, _ accountName: String) -> String {
let localizedText = NSLocalizedString("An error occurred while processing the “%@” account: %@", comment: "Unknown error")
return NSString.localizedStringWithFormat(localizedText as NSString, accountName, error.localizedDescription) as String
}
func isCredentialsError(status: Int) -> Bool {
return status == 401 || status == 403
}
}

View File

@ -0,0 +1,12 @@
import XCTest
@testable import CommonErrors
final class CommonErrorsTests: XCTestCase {
func testExample() throws {
// XCTest Documentation
// https://developer.apple.com/documentation/xctest
// Defining Test Cases and Test Methods
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
}
}

View File

@ -1303,6 +1303,7 @@
840D617E2029031C009BC708 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetNewsWire_iOSTests.swift; sourceTree = "<group>"; };
840D61972029031D009BC708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8410C4A62BC221C900D4F799 /* CommonErrors */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = CommonErrors; sourceTree = "<group>"; };
841550F42B9E3F8000D4B345 /* Database */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Database; sourceTree = "<group>"; };
841550F52B9E4D6800D4B345 /* FMDB */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FMDB; sourceTree = "<group>"; };
84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkCommandValidationStatus.swift; sourceTree = "<group>"; };
@ -2356,6 +2357,7 @@
51C452B22265141B00C03939 /* Frameworks */,
51CD32C624D2DEF9009ABAEF /* Account */,
84CC98D92BC1DD25006A05C9 /* ReaderAPI */,
8410C4A62BC221C900D4F799 /* CommonErrors */,
51CD32C424D2CF1D009ABAEF /* Articles */,
51CD32C324D2CD57009ABAEF /* ArticlesDatabase */,
51CD32C724D2E06C009ABAEF /* Secrets */,

View File

@ -11,12 +11,20 @@ let package = Package(
targets: ["ReaderAPI"]),
],
dependencies: [
.package(path: "../FoundationExtras")
.package(path: "../FoundationExtras"),
.package(path: "../Web"),
.package(path: "../Secrets"),
.package(path: "../CommonErrors"),
],
targets: [
.target(
name: "ReaderAPI",
dependencies: ["FoundationExtras"],
dependencies: [
"FoundationExtras",
"Web",
"Secrets",
"CommonErrors"
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]

View File

@ -9,14 +9,23 @@
import Foundation
import Web
import Secrets
import ReaderAPI
import CommonErrors
enum CreateReaderAPISubscriptionResult {
public protocol ReaderAPICallerDelegate: AnyObject {
var endpointURL: URL? { get }
var lastArticleFetchStartTime: Date? { get set }
var lastArticleFetchEndTime: Date? { get set }
}
public enum CreateReaderAPISubscriptionResult: Sendable {
case created(ReaderAPISubscription)
case notFound
}
@MainActor final class ReaderAPICaller: NSObject {
@MainActor final class ReaderAPICaller {
enum ItemIDType {
case unread
@ -54,7 +63,7 @@ enum CreateReaderAPISubscriptionResult {
private var accessToken: String?
weak var accountMetadata: AccountMetadata?
weak var delegate: ReaderAPICallerDelegate?
var variant: ReaderAPIVariant = .generic
var credentials: Credentials?
@ -69,10 +78,7 @@ enum CreateReaderAPISubscriptionResult {
get {
switch variant {
case .generic, .freshRSS:
guard let accountMetadata = accountMetadata else {
return nil
}
return accountMetadata.endpointURL
return delegate?.endpointURL
default:
return URL(string: variant.host)
}
@ -80,14 +86,14 @@ enum CreateReaderAPISubscriptionResult {
}
init(transport: Transport, secretsProvider: SecretsProvider) {
self.transport = transport
self.secretsProvider = secretsProvider
var urlHostAllowed = CharacterSet.urlHostAllowed
urlHostAllowed.remove("+")
urlHostAllowed.remove("&")
uriComponentAllowed = urlHostAllowed
super.init()
self.uriComponentAllowed = urlHostAllowed
}
func cancelAll() {
@ -100,7 +106,7 @@ enum CreateReaderAPISubscriptionResult {
throw CredentialsError.incompleteCredentials
}
var request = URLRequest(url: endpoint.appendingPathComponent(ReaderAPIEndpoints.login.rawValue), credentials: credentials)
var request = URLRequest(url: endpoint.appendingPathComponent(ReaderAPIEndpoints.login.rawValue), readerAPICredentials: credentials)
addVariantHeaders(&request)
do {
@ -134,7 +140,7 @@ enum CreateReaderAPISubscriptionResult {
} catch {
if let transportError = error as? TransportError, case .httpError(let code) = transportError, code == 404 {
throw ReaderAPIAccountDelegateError.urlNotFound
throw ReaderAPIError.urlNotFound
} else {
throw error
}
@ -153,7 +159,7 @@ enum CreateReaderAPISubscriptionResult {
throw CredentialsError.incompleteCredentials
}
var request = URLRequest(url: endpoint.appendingPathComponent(ReaderAPIEndpoints.token.rawValue), credentials: credentials)
var request = URLRequest(url: endpoint.appendingPathComponent(ReaderAPIEndpoints.token.rawValue), readerAPICredentials: credentials)
addVariantHeaders(&request)
let (_, data) = try await transport.send(request: request)
@ -185,7 +191,7 @@ enum CreateReaderAPISubscriptionResult {
throw TransportError.noURL
}
var request = URLRequest(url: callURL, credentials: credentials)
var request = URLRequest(url: callURL, readerAPICredentials: credentials)
addVariantHeaders(&request)
let (_, wrapper) = try await transport.send(request: request, resultType: ReaderAPITagContainer.self)
@ -200,13 +206,13 @@ enum CreateReaderAPISubscriptionResult {
let token = try await requestAuthorizationToken(endpoint: baseURL)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.renameTag.rawValue), credentials: self.credentials)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.renameTag.rawValue), readerAPICredentials: self.credentials)
self.addVariantHeaders(&request)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
guard let encodedOldName = self.encodeForURLPath(oldName), let encodedNewName = self.encodeForURLPath(newName) else {
throw ReaderAPIAccountDelegateError.invalidParameter
throw ReaderAPIError.invalidParameter
}
let oldTagName = "user/-/label/\(encodedOldName)"
@ -217,18 +223,15 @@ enum CreateReaderAPISubscriptionResult {
}
func deleteTag(folder: Folder) async throws {
func deleteTag(folderExternalID: String) async throws {
guard let baseURL = apiBaseURL else {
throw CredentialsError.incompleteCredentials
}
guard let folderExternalID = folder.externalID else {
throw ReaderAPIAccountDelegateError.invalidParameter
}
let token = try await self.requestAuthorizationToken(endpoint: baseURL)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.disableTag.rawValue), credentials: self.credentials)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.disableTag.rawValue), readerAPICredentials: self.credentials)
self.addVariantHeaders(&request)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
@ -252,14 +255,14 @@ enum CreateReaderAPISubscriptionResult {
throw TransportError.noURL
}
var request = URLRequest(url: callURL, credentials: credentials)
var request = URLRequest(url: callURL, readerAPICredentials: credentials)
addVariantHeaders(&request)
let (_, container) = try await transport.send(request: request, resultType: ReaderAPISubscriptionContainer.self)
return container?.subscriptions
}
func createSubscription(url: String, name: String?, folder: Folder?) async throws -> CreateReaderAPISubscriptionResult {
func createSubscription(url: String, name: String?) async throws -> CreateReaderAPISubscriptionResult {
guard let baseURL = apiBaseURL else {
throw CredentialsError.incompleteCredentials
@ -270,13 +273,13 @@ enum CreateReaderAPISubscriptionResult {
let callURL = baseURL
.appendingPathComponent(ReaderAPIEndpoints.subscriptionAdd.rawValue)
var request = URLRequest(url: callURL, credentials: self.credentials)
var request = URLRequest(url: callURL, readerAPICredentials: self.credentials)
self.addVariantHeaders(&request)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
guard let encodedFeedURL = self.encodeForURLPath(url) else {
throw ReaderAPIAccountDelegateError.invalidParameter
throw ReaderAPIError.invalidParameter
}
let postData = "T=\(token)&quickadd=\(encodedFeedURL)".data(using: String.Encoding.utf8)
@ -293,10 +296,10 @@ enum CreateReaderAPISubscriptionResult {
// There is no call to get a single subscription entry, so we get them all,
// look up the one we just subscribed to and return that
guard let subscriptions = try await retrieveSubscriptions() else {
throw AccountError.createErrorNotFound
throw CommonError.createErrorNotFound
}
guard let subscription = subscriptions.first(where: { $0.feedID == subResult.streamID }) else {
throw AccountError.createErrorNotFound
throw CommonError.createErrorNotFound
}
return .created(subscription)
@ -315,7 +318,7 @@ enum CreateReaderAPISubscriptionResult {
let token = try await self.requestAuthorizationToken(endpoint: baseURL)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.subscriptionEdit.rawValue), credentials: self.credentials)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.subscriptionEdit.rawValue), readerAPICredentials: self.credentials)
self.addVariantHeaders(&request)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
@ -343,16 +346,15 @@ enum CreateReaderAPISubscriptionResult {
private func changeSubscription(subscriptionID: String, removeTagName: String? = nil, addTagName: String? = nil, title: String? = nil) async throws {
guard removeTagName != nil || addTagName != nil || title != nil else {
throw ReaderAPIAccountDelegateError.invalidParameter
throw ReaderAPIError.invalidParameter
}
guard let baseURL = apiBaseURL else {
throw CredentialsError.incompleteCredentials
return
}
let token = try await requestAuthorizationToken(endpoint: baseURL)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.subscriptionEdit.rawValue), credentials: self.credentials)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.subscriptionEdit.rawValue), readerAPICredentials: self.credentials)
self.addVariantHeaders(&request)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
@ -383,7 +385,7 @@ enum CreateReaderAPISubscriptionResult {
let token = try await requestAuthorizationToken(endpoint: baseURL)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.contents.rawValue), credentials: self.credentials)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.contents.rawValue), readerAPICredentials: self.credentials)
self.addVariantHeaders(&request)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
@ -404,7 +406,7 @@ enum CreateReaderAPISubscriptionResult {
let (_, entryWrapper) = try await transport.send(request: request, method: HTTPMethod.post, data: postData!, resultType: ReaderAPIEntryWrapper.self)
guard let entryWrapper else {
throw ReaderAPIAccountDelegateError.invalidResponse
throw ReaderAPIError.invalidResponse
}
return entryWrapper.entries
@ -424,7 +426,7 @@ enum CreateReaderAPISubscriptionResult {
switch type {
case .allForAccount:
let since: Date = {
if let lastArticleFetch = self.accountMetadata?.lastArticleFetchStartTime {
if let lastArticleFetch = delegate?.lastArticleFetchStartTime {
return lastArticleFetch
} else {
return Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date()
@ -436,7 +438,7 @@ enum CreateReaderAPISubscriptionResult {
queryItems.append(URLQueryItem(name: "s", value: ReaderStreams.readingList.rawValue))
case .allForFeed:
guard let feedID else {
throw ReaderAPIAccountDelegateError.invalidParameter
throw ReaderAPIError.invalidParameter
}
let sinceTimeInterval = (Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date()).timeIntervalSince1970
queryItems.append(URLQueryItem(name: "ot", value: String(Int(sinceTimeInterval))))
@ -456,7 +458,7 @@ enum CreateReaderAPISubscriptionResult {
throw TransportError.noURL
}
var request: URLRequest = URLRequest(url: callURL, credentials: credentials)
var request: URLRequest = URLRequest(url: callURL, readerAPICredentials: credentials)
addVariantHeaders(&request)
let (response, entries) = try await transport.send(request: request, resultType: ReaderAPIReferenceWrapper.self)
@ -475,14 +477,14 @@ enum CreateReaderAPISubscriptionResult {
guard let continuation else {
if type == .allForAccount {
self.accountMetadata?.lastArticleFetchStartTime = dateInfo?.date
self.accountMetadata?.lastArticleFetchEndTime = Date()
delegate?.lastArticleFetchStartTime = dateInfo?.date
delegate?.lastArticleFetchEndTime = Date()
}
return itemIDs
}
guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
throw ReaderAPIAccountDelegateError.invalidParameter
throw ReaderAPIError.invalidParameter
}
var queryItems = urlComponents.queryItems!.filter({ $0.name != "c" })
@ -493,7 +495,7 @@ enum CreateReaderAPISubscriptionResult {
throw TransportError.noURL
}
var request: URLRequest = URLRequest(url: callURL, credentials: credentials)
var request: URLRequest = URLRequest(url: callURL, readerAPICredentials: credentials)
addVariantHeaders(&request)
let (_, entries) = try await self.transport.send(request: request, resultType: ReaderAPIReferenceWrapper.self)
@ -554,7 +556,7 @@ private extension ReaderAPICaller {
let token = try await requestAuthorizationToken(endpoint: baseURL)
// Do POST asking for data about all the new articles
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.editTag.rawValue), credentials: self.credentials)
var request = URLRequest(url: baseURL.appendingPathComponent(ReaderAPIEndpoints.editTag.rawValue), readerAPICredentials: self.credentials)
self.addVariantHeaders(&request)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"

View File

@ -0,0 +1,29 @@
//
// ReaderAPIError.swift
//
//
// Created by Brent Simmons on 4/6/24.
//
import Foundation
public enum ReaderAPIError: LocalizedError {
case unknown
case invalidParameter
case invalidResponse
case urlNotFound
public var errorDescription: String? {
switch self {
case .unknown:
return NSLocalizedString("An unexpected error occurred.", comment: "An unexpected error occurred.")
case .invalidParameter:
return NSLocalizedString("An invalid parameter was passed.", comment: "An invalid parameter was passed.")
case .invalidResponse:
return NSLocalizedString("There was an invalid response from the server.", comment: "There was an invalid response from the server.")
case .urlNotFound:
return NSLocalizedString("The API URL wasn't found.", comment: "The API URL wasn't found.")
}
}
}

View File

@ -0,0 +1,55 @@
//
// URLRequest+ReaderAPI.swift
//
//
// Created by Brent Simmons on 4/6/24.
//
import Foundation
import Secrets
import Web
extension URLRequest {
init(url: URL, readerAPICredentials: Credentials?, conditionalGet: HTTPConditionalGetInfo? = nil) {
self.init(url: url)
guard let credentials = readerAPICredentials else {
return
}
let credentialsType = credentials.type
precondition(credentialsType == .readerBasic || credentialsType == .readerAPIKey)
if credentialsType == .readerBasic {
setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
httpMethod = "POST"
var postData = URLComponents()
postData.queryItems = [
URLQueryItem(name: "Email", value: credentials.username),
URLQueryItem(name: "Passwd", value: credentials.secret)
]
httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8)
} else if credentialsType == .readerAPIKey {
let auth = "GoogleLogin auth=\(credentials.secret)"
setValue(auth, forHTTPHeaderField: HTTPRequestHeader.authorization)
}
guard let conditionalGet = conditionalGet else {
return
}
// Bug seen in the wild: lastModified with last possible 32-bit date, which is in 2038. Ignore those.
// TODO: drop this check in late 2037.
if let lastModified = conditionalGet.lastModified, !lastModified.contains("2038") {
setValue(lastModified, forHTTPHeaderField: HTTPRequestHeader.ifModifiedSince)
}
if let etag = conditionalGet.etag {
setValue(etag, forHTTPHeaderField: HTTPRequestHeader.ifNoneMatch)
}
}
}