| | |
| | | fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason |
| | | |
| | | /// Used to represent whether a validation succeeded or failed. |
| | | public typealias ValidationResult = Result<Void, Error> |
| | | public typealias ValidationResult = Result<Void, any(Error & Sendable)> |
| | | |
| | | fileprivate struct MIMEType { |
| | | let type: String |
| | |
| | | func matches(_ mime: MIMEType) -> Bool { |
| | | switch (type, subtype) { |
| | | case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"): |
| | | return true |
| | | true |
| | | default: |
| | | return false |
| | | false |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S, |
| | | response: HTTPURLResponse, |
| | | data: Data?) |
| | | isEmpty: Bool) |
| | | -> ValidationResult |
| | | where S.Iterator.Element == String { |
| | | guard let data, !data.isEmpty else { return .success(()) } |
| | | guard !isEmpty else { return .success(()) } |
| | | |
| | | return validate(contentType: acceptableContentTypes, response: response) |
| | | } |
| | |
| | | extension DataRequest { |
| | | /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the |
| | | /// request was valid. |
| | | public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult |
| | | public typealias Validation = @Sendable (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult |
| | | |
| | | /// Validates that the response has a status code in the specified sequence. |
| | | /// |
| | |
| | | /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { |
| | | public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int, S: Sendable { |
| | | validate { [unowned self] _, response, _ in |
| | | self.validate(statusCode: acceptableStatusCodes, response: response) |
| | | } |
| | |
| | | /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. |
| | | /// |
| | | /// - returns: The request. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { |
| | | public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @Sendable @autoclosure () -> S) -> Self where S.Iterator.Element == String, S: Sendable { |
| | | validate { [unowned self] _, response, data in |
| | | self.validate(contentType: acceptableContentTypes(), response: response, data: data) |
| | | self.validate(contentType: acceptableContentTypes(), response: response, isEmpty: (data == nil || data?.isEmpty == true)) |
| | | } |
| | | } |
| | | |
| | |
| | | /// - returns: The request. |
| | | @discardableResult |
| | | public func validate() -> Self { |
| | | let contentTypes: () -> [String] = { [unowned self] in |
| | | let contentTypes: @Sendable () -> [String] = { [unowned self] in |
| | | acceptableContentTypes |
| | | } |
| | | return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) |
| | |
| | | extension DataStreamRequest { |
| | | /// A closure used to validate a request that takes a `URLRequest` and `HTTPURLResponse` and returns whether the |
| | | /// request was valid. |
| | | public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> ValidationResult |
| | | public typealias Validation = @Sendable (_ request: URLRequest?, _ response: HTTPURLResponse) -> ValidationResult |
| | | |
| | | /// Validates that the response has a status code in the specified sequence. |
| | | /// |
| | |
| | | /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { |
| | | public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int, S: Sendable { |
| | | validate { [unowned self] _, response in |
| | | self.validate(statusCode: acceptableStatusCodes, response: response) |
| | | } |
| | |
| | | /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. |
| | | /// |
| | | /// - returns: The request. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { |
| | | public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @Sendable @autoclosure () -> S) -> Self where S.Iterator.Element == String, S: Sendable { |
| | | validate { [unowned self] _, response in |
| | | self.validate(contentType: acceptableContentTypes(), response: response) |
| | | } |
| | |
| | | /// - Returns: The instance. |
| | | @discardableResult |
| | | public func validate() -> Self { |
| | | let contentTypes: () -> [String] = { [unowned self] in |
| | | let contentTypes: @Sendable () -> [String] = { [unowned self] in |
| | | acceptableContentTypes |
| | | } |
| | | return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) |
| | |
| | | extension DownloadRequest { |
| | | /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a |
| | | /// destination URL, and returns whether the request was valid. |
| | | public typealias Validation = (_ request: URLRequest?, |
| | | _ response: HTTPURLResponse, |
| | | _ fileURL: URL?) |
| | | public typealias Validation = @Sendable (_ request: URLRequest?, |
| | | _ response: HTTPURLResponse, |
| | | _ fileURL: URL?) |
| | | -> ValidationResult |
| | | |
| | | /// Validates that the response has a status code in the specified sequence. |
| | |
| | | /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. |
| | | /// |
| | | /// - Returns: The instance. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { |
| | | public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int, S: Sendable { |
| | | validate { [unowned self] _, response, _ in |
| | | self.validate(statusCode: acceptableStatusCodes, response: response) |
| | | } |
| | | } |
| | | |
| | | /// Validates that the response has a content type in the specified sequence. |
| | | /// Validates that the response has a `Content-Type` in the specified sequence. |
| | | /// |
| | | /// If validation fails, subsequent calls to response handlers will have an associated error. |
| | | /// |
| | | /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. |
| | | /// |
| | | /// - returns: The request. |
| | | @preconcurrency |
| | | @discardableResult |
| | | public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { |
| | | public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @Sendable @autoclosure () -> S) -> Self where S.Iterator.Element == String, S: Sendable { |
| | | validate { [unowned self] _, response, fileURL in |
| | | guard let validFileURL = fileURL else { |
| | | guard let fileURL else { |
| | | return .failure(AFError.responseValidationFailed(reason: .dataFileNil)) |
| | | } |
| | | |
| | | do { |
| | | let data = try Data(contentsOf: validFileURL) |
| | | return self.validate(contentType: acceptableContentTypes(), response: response, data: data) |
| | | let isEmpty = try (fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0) == 0 |
| | | return self.validate(contentType: acceptableContentTypes(), response: response, isEmpty: isEmpty) |
| | | } catch { |
| | | return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL))) |
| | | return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: fileURL))) |
| | | } |
| | | } |
| | | } |
| | |
| | | /// - returns: The request. |
| | | @discardableResult |
| | | public func validate() -> Self { |
| | | let contentTypes = { [unowned self] in |
| | | let contentTypes: @Sendable () -> [String] = { [unowned self] in |
| | | acceptableContentTypes |
| | | } |
| | | return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) |