diff --git a/Apollo.xcodeproj/project.pbxproj b/Apollo.xcodeproj/project.pbxproj index 5241743ae7..c15264c95d 100644 --- a/Apollo.xcodeproj/project.pbxproj +++ b/Apollo.xcodeproj/project.pbxproj @@ -21,6 +21,9 @@ 9BDE43D122C6655300FD7C7F /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BDE43D022C6655200FD7C7F /* Cancellable.swift */; }; 9BDE43DD22C6705300FD7C7F /* GraphQLHTTPResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BDE43DC22C6705300FD7C7F /* GraphQLHTTPResponseError.swift */; }; 9BDE43DF22C6708600FD7C7F /* GraphQLHTTPRequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BDE43DE22C6708600FD7C7F /* GraphQLHTTPRequestError.swift */; }; + 9BE071AD2368D08700FA5952 /* Collection+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE071AC2368D08700FA5952 /* Collection+Helpers.swift */; }; + 9BE071AF2368D34D00FA5952 /* Matchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE071AE2368D34D00FA5952 /* Matchable.swift */; }; + 9BE071B12368D3F500FA5952 /* Dictionary+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE071B02368D3F500FA5952 /* Dictionary+Helpers.swift */; }; 9BEDC79E22E5D2CF00549BF6 /* RequestCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BEDC79D22E5D2CF00549BF6 /* RequestCreator.swift */; }; 9BF1A94F22CA5784005292C2 /* HTTPTransportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BF1A94C22CA54F9005292C2 /* HTTPTransportTests.swift */; }; 9BF1A95122CA6E71005292C2 /* GraphQLGETTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BF1A95022CA6E71005292C2 /* GraphQLGETTransformer.swift */; }; @@ -36,7 +39,7 @@ 9F438D081E6C30B1007BDC1A /* StarWarsAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FCE2CFA1E6C213D00E34457 /* StarWarsAPI.framework */; }; 9F533AB31E6C4A4200CBE097 /* BatchedLoadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F438D0B1E6C494C007BDC1A /* BatchedLoadTests.swift */; }; 9F55347B1DE1DB2100E54264 /* ApolloStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55347A1DE1DB2100E54264 /* ApolloStore.swift */; }; - 9F578D901D8D2CB300C0EA36 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */; }; + 9F578D901D8D2CB300C0EA36 /* HTTPURLResponse+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F578D8F1D8D2CB300C0EA36 /* HTTPURLResponse+Helpers.swift */; }; 9F65B1211EC106F30090B25F /* Apollo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FC750441D2A532C00458D91 /* Apollo.framework */; }; 9F69FFA91D42855900E000B1 /* NetworkTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F69FFA81D42855900E000B1 /* NetworkTransport.swift */; }; 9F7BA89922927A3700999B3B /* ResponsePath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7BA89822927A3700999B3B /* ResponsePath.swift */; }; @@ -101,7 +104,7 @@ 9FE1C6E71E634C8D00C02284 /* PromiseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE1C6E61E634C8D00C02284 /* PromiseTests.swift */; }; 9FE941D01E62C771007CDD89 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FE941CF1E62C771007CDD89 /* Promise.swift */; }; 9FEB050D1DB5732300DA3B44 /* JSONSerializationFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEB050C1DB5732300DA3B44 /* JSONSerializationFormat.swift */; }; - 9FEC15B41E681DAD00D461B4 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEC15B31E681DAD00D461B4 /* Collections.swift */; }; + 9FEC15B41E681DAD00D461B4 /* GroupedSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEC15B31E681DAD00D461B4 /* GroupedSequence.swift */; }; 9FF33D811E48B98200F608A4 /* HTTPNetworkTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4DAF2D1E48B84B00EBFF0B /* HTTPNetworkTransport.swift */; }; 9FF90A611DDDEB100034C3B6 /* GraphQLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF90A5B1DDDEB100034C3B6 /* GraphQLResponse.swift */; }; 9FF90A651DDDEB100034C3B6 /* GraphQLExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF90A5C1DDDEB100034C3B6 /* GraphQLExecutor.swift */; }; @@ -283,6 +286,9 @@ 9BDE43D022C6655200FD7C7F /* Cancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cancellable.swift; sourceTree = ""; }; 9BDE43DC22C6705300FD7C7F /* GraphQLHTTPResponseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPResponseError.swift; sourceTree = ""; }; 9BDE43DE22C6708600FD7C7F /* GraphQLHTTPRequestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPRequestError.swift; sourceTree = ""; }; + 9BE071AC2368D08700FA5952 /* Collection+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Helpers.swift"; sourceTree = ""; }; + 9BE071AE2368D34D00FA5952 /* Matchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Matchable.swift; sourceTree = ""; }; + 9BE071B02368D3F500FA5952 /* Dictionary+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Helpers.swift"; sourceTree = ""; }; 9BEDC79D22E5D2CF00549BF6 /* RequestCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestCreator.swift; sourceTree = ""; }; 9BF1A94C22CA54F9005292C2 /* HTTPTransportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPTransportTests.swift; sourceTree = ""; }; 9BF1A95022CA6E71005292C2 /* GraphQLGETTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLGETTransformer.swift; sourceTree = ""; }; @@ -296,7 +302,7 @@ 9F438D0B1E6C494C007BDC1A /* BatchedLoadTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatchedLoadTests.swift; sourceTree = ""; }; 9F4DAF2D1E48B84B00EBFF0B /* HTTPNetworkTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPNetworkTransport.swift; sourceTree = ""; }; 9F55347A1DE1DB2100E54264 /* ApolloStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApolloStore.swift; sourceTree = ""; }; - 9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + 9F578D8F1D8D2CB300C0EA36 /* HTTPURLResponse+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPURLResponse+Helpers.swift"; sourceTree = ""; }; 9F69FFA81D42855900E000B1 /* NetworkTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkTransport.swift; sourceTree = ""; }; 9F7BA89822927A3700999B3B /* ResponsePath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponsePath.swift; sourceTree = ""; }; 9F8622F71EC2004200C38162 /* ReadWriteFromStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadWriteFromStoreTests.swift; sourceTree = ""; }; @@ -379,7 +385,7 @@ 9FE1C6E61E634C8D00C02284 /* PromiseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromiseTests.swift; sourceTree = ""; }; 9FE941CF1E62C771007CDD89 /* Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = ""; }; 9FEB050C1DB5732300DA3B44 /* JSONSerializationFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONSerializationFormat.swift; sourceTree = ""; }; - 9FEC15B31E681DAD00D461B4 /* Collections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collections.swift; sourceTree = ""; }; + 9FEC15B31E681DAD00D461B4 /* GroupedSequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupedSequence.swift; sourceTree = ""; }; 9FF90A5B1DDDEB100034C3B6 /* GraphQLResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLResponse.swift; sourceTree = ""; }; 9FF90A5C1DDDEB100034C3B6 /* GraphQLExecutor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLExecutor.swift; sourceTree = ""; }; 9FF90A6A1DDDEB420034C3B6 /* InputValueEncodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputValueEncodingTests.swift; sourceTree = ""; }; @@ -530,6 +536,7 @@ isa = PBXGroup; children = ( 9BDE43D022C6655200FD7C7F /* Cancellable.swift */, + 9BE071AE2368D34D00FA5952 /* Matchable.swift */, ); name = Protocols; sourceTree = ""; @@ -727,14 +734,16 @@ children = ( 9FCDFD221E33A0D8007519DC /* AsynchronousOperation.swift */, 9B1CCDD82360F02C007C9032 /* Bundle+Helpers.swift */, + 9BE071AC2368D08700FA5952 /* Collection+Helpers.swift */, + 9BE071B02368D3F500FA5952 /* Dictionary+Helpers.swift */, 9BA3130D2302BEA5007B7FC5 /* DispatchQueue+Optional.swift */, + 9FEC15B31E681DAD00D461B4 /* GroupedSequence.swift */, + 9F578D8F1D8D2CB300C0EA36 /* HTTPURLResponse+Helpers.swift */, + 9FADC8491E6B0B2300C677E6 /* Locking.swift */, 9FE941CF1E62C771007CDD89 /* Promise.swift */, 9BA1245D22DE116B00BF1D24 /* Result+Helpers.swift */, 9F19D8431EED568200C57247 /* ResultOrPromise.swift */, - 9FEC15B31E681DAD00D461B4 /* Collections.swift */, - 9FADC8491E6B0B2300C677E6 /* Locking.swift */, 9B1A38522332AF6F00325FB4 /* String+SHA.swift */, - 9F578D8F1D8D2CB300C0EA36 /* Utilities.swift */, ); name = Utilities; sourceTree = ""; @@ -1216,7 +1225,9 @@ 9F86B68B1E6438D700B885FF /* GraphQLSelectionSetMapper.swift in Sources */, 9F55347B1DE1DB2100E54264 /* ApolloStore.swift in Sources */, 9BDE43D122C6655300FD7C7F /* Cancellable.swift in Sources */, + 9BE071B12368D3F500FA5952 /* Dictionary+Helpers.swift in Sources */, 9F69FFA91D42855900E000B1 /* NetworkTransport.swift in Sources */, + 9BE071AF2368D34D00FA5952 /* Matchable.swift in Sources */, 9FCDFD291E33D0CE007519DC /* GraphQLQueryWatcher.swift in Sources */, 9FC2333D1E66BBF7001E4541 /* GraphQLDependencyTracker.swift in Sources */, 9F19D8441EED568200C57247 /* ResultOrPromise.swift in Sources */, @@ -1232,8 +1243,8 @@ C377CCA922D798BD00572E03 /* GraphQLFile.swift in Sources */, 9FC9A9CC1E2FD0760023C4D5 /* Record.swift in Sources */, 9FC4B9201D2A6F8D0046A641 /* JSON.swift in Sources */, - 9FEC15B41E681DAD00D461B4 /* Collections.swift in Sources */, - 9F578D901D8D2CB300C0EA36 /* Utilities.swift in Sources */, + 9FEC15B41E681DAD00D461B4 /* GroupedSequence.swift in Sources */, + 9F578D901D8D2CB300C0EA36 /* HTTPURLResponse+Helpers.swift in Sources */, 9F7BA89922927A3700999B3B /* ResponsePath.swift in Sources */, 9FC9A9BD1E2C271C0023C4D5 /* RecordSet.swift in Sources */, 9BF1A95122CA6E71005292C2 /* GraphQLGETTransformer.swift in Sources */, @@ -1242,6 +1253,7 @@ 9F27D4641D40379500715680 /* JSONStandardTypeConversions.swift in Sources */, 9B1A38532332AF6F00325FB4 /* String+SHA.swift in Sources */, 9BEDC79E22E5D2CF00549BF6 /* RequestCreator.swift in Sources */, + 9BE071AD2368D08700FA5952 /* Collection+Helpers.swift in Sources */, 9FA6F3681E65DF4700BF8D73 /* GraphQLResultAccumulator.swift in Sources */, 9FF90A651DDDEB100034C3B6 /* GraphQLExecutor.swift in Sources */, 9FC750611D2A59C300458D91 /* GraphQLOperation.swift in Sources */, diff --git a/Sources/Apollo/Bundle+Helpers.swift b/Sources/Apollo/Bundle+Helpers.swift index 6730fa1376..e87f2481e8 100644 --- a/Sources/Apollo/Bundle+Helpers.swift +++ b/Sources/Apollo/Bundle+Helpers.swift @@ -1,11 +1,3 @@ -// -// Bundle+Helpers.swift -// Apollo -// -// Created by Ellen Shapiro on 10/23/19. -// Copyright © 2019 Apollo GraphQL. All rights reserved. -// - import Foundation extension Bundle { diff --git a/Sources/Apollo/Collection+Helpers.swift b/Sources/Apollo/Collection+Helpers.swift new file mode 100644 index 0000000000..eef0020680 --- /dev/null +++ b/Sources/Apollo/Collection+Helpers.swift @@ -0,0 +1,65 @@ +import Foundation + +// MARK: - Emptiness + Optionality + +extension Collection { + + /// Convenience helper to make `guard` statements more readable + /// + /// - returns: `true` if the collection has contents. + var isNotEmpty: Bool { + return !self.isEmpty + } +} + +extension Optional where Wrapped: Collection { + + /// - returns: `true` if the collection is empty or nil + var isEmptyOrNil: Bool { + switch self { + case .none: + return true + case .some(let collection): + return collection.isEmpty + } + } + + /// - returns: `true` if the collection is non-nil AND has contents. + var isNotEmpty: Bool { + switch self { + case .none: + return false + case .some(let collection): + return collection.isNotEmpty + } + } +} + +// MARK: - Unzipping +// MARK: Arrays of tuples to tuples of arrays + +public func unzip(_ array: [(Element1, Element2)]) -> ([Element1], [Element2]) { + var array1: [Element1] = [] + var array2: [Element2] = [] + + for element in array { + array1.append(element.0) + array2.append(element.1) + } + + return (array1, array2) +} + +public func unzip(_ array: [(Element1, Element2, Element3)]) -> ([Element1], [Element2], [Element3]) { + var array1: [Element1] = [] + var array2: [Element2] = [] + var array3: [Element3] = [] + + for element in array { + array1.append(element.0) + array2.append(element.1) + array3.append(element.2) + } + + return (array1, array2, array3) +} diff --git a/Sources/Apollo/Collections.swift b/Sources/Apollo/Collections.swift deleted file mode 100644 index 6ad5532b1a..0000000000 --- a/Sources/Apollo/Collections.swift +++ /dev/null @@ -1,94 +0,0 @@ -public extension Dictionary { - static func += (lhs: inout Dictionary, rhs: Dictionary) { - lhs.merge(rhs) { (_, new) in new } - } -} - -extension Dictionary { - init(_ entries: S) where S.Iterator.Element == Element { - self = Dictionary(minimumCapacity: entries.underestimatedCount) - for (key, value) in entries { - self[key] = value - } - } -} - -struct GroupedSequence { - private(set) var keys: [Key] = [] - fileprivate var groupsForKeys: [[Value]] = [] - - mutating func append(value: Value, forKey key: Key) -> (Int, Int) { - if let index = keys.firstIndex(where: { $0 == key }) { - groupsForKeys[index].append(value) - return (index, groupsForKeys[index].endIndex - 1) - } else { - keys.append(key) - groupsForKeys.append([value]) - return (keys.endIndex - 1, 0) - } - } -} - -extension GroupedSequence: Sequence { - func makeIterator() -> GroupedSequenceIterator { - return GroupedSequenceIterator(base: self) - } -} - -struct GroupedSequenceIterator: IteratorProtocol { - private var base: GroupedSequence - - private var keyIterator: EnumeratedSequence>.Iterator - - init(base: GroupedSequence) { - self.base = base - keyIterator = base.keys.enumerated().makeIterator() - } - - mutating func next() -> (Key, [Value])? { - if let (index, key) = keyIterator.next() { - let values = base.groupsForKeys[index] - return (key, values) - } else { - return nil - } - } -} - -public func unzip(_ array: [(Element1, Element2)]) -> ([Element1], [Element2]) { - var array1: [Element1] = [] - var array2: [Element2] = [] - - for element in array { - array1.append(element.0) - array2.append(element.1) - } - - return (array1, array2) -} - -public func unzip(_ array: [(Element1, Element2, Element3)]) -> ([Element1], [Element2], [Element3]) { - var array1: [Element1] = [] - var array2: [Element2] = [] - var array3: [Element3] = [] - - for element in array { - array1.append(element.0) - array2.append(element.1) - array3.append(element.2) - } - - return (array1, array2, array3) -} - -public func unzip(_ array: [[Element]], count: Int) -> [[Element]] { - var unzippedArray: [[Element]] = Array(repeating: [], count: count) - - for valuesForElement in array { - for (index, value) in valuesForElement.enumerated() { - unzippedArray[index].append(value) - } - } - - return unzippedArray -} diff --git a/Sources/Apollo/Dictionary+Helpers.swift b/Sources/Apollo/Dictionary+Helpers.swift new file mode 100644 index 0000000000..27832500f9 --- /dev/null +++ b/Sources/Apollo/Dictionary+Helpers.swift @@ -0,0 +1,14 @@ +public extension Dictionary { + static func += (lhs: inout Dictionary, rhs: Dictionary) { + lhs.merge(rhs) { (_, new) in new } + } +} + +extension Dictionary { + init(_ entries: S) where S.Iterator.Element == Element { + self = Dictionary(minimumCapacity: entries.underestimatedCount) + for (key, value) in entries { + self[key] = value + } + } +} diff --git a/Sources/Apollo/DispatchQueue+Optional.swift b/Sources/Apollo/DispatchQueue+Optional.swift index 38e0bcf031..0847c53dea 100644 --- a/Sources/Apollo/DispatchQueue+Optional.swift +++ b/Sources/Apollo/DispatchQueue+Optional.swift @@ -1,11 +1,3 @@ -// -// DispatchQueue+Optional.swift -// Apollo -// -// Created by Ellen Shapiro on 8/13/19. -// Copyright © 2019 Apollo GraphQL. All rights reserved. -// - import Foundation public extension DispatchQueue { diff --git a/Sources/Apollo/GraphQLSelectionSet.swift b/Sources/Apollo/GraphQLSelectionSet.swift index 6be1219a5e..67c1b51d1d 100644 --- a/Sources/Apollo/GraphQLSelectionSet.swift +++ b/Sources/Apollo/GraphQLSelectionSet.swift @@ -51,9 +51,11 @@ public struct GraphQLField: GraphQLSelection { } func cacheKey(with variables: [String: JSONEncodable]?) throws -> String { - if let argumentValues = try arguments?.evaluate(with: variables), !argumentValues.isEmpty { - let argumentsKey = orderIndependentKey(for: argumentValues) - return "\(name)(\(argumentsKey))" + if + let argumentValues = try arguments?.evaluate(with: variables), + argumentValues.isNotEmpty { + let argumentsKey = orderIndependentKey(for: argumentValues) + return "\(name)(\(argumentsKey))" } else { return name } diff --git a/Sources/Apollo/GroupedSequence.swift b/Sources/Apollo/GroupedSequence.swift new file mode 100644 index 0000000000..231e759105 --- /dev/null +++ b/Sources/Apollo/GroupedSequence.swift @@ -0,0 +1,41 @@ +struct GroupedSequence { + private(set) var keys: [Key] = [] + fileprivate var groupsForKeys: [[Value]] = [] + + mutating func append(value: Value, forKey key: Key) -> (Int, Int) { + if let index = keys.firstIndex(where: { $0 == key }) { + groupsForKeys[index].append(value) + return (index, groupsForKeys[index].endIndex - 1) + } else { + keys.append(key) + groupsForKeys.append([value]) + return (keys.endIndex - 1, 0) + } + } +} + +extension GroupedSequence: Sequence { + func makeIterator() -> GroupedSequenceIterator { + return GroupedSequenceIterator(base: self) + } +} + +struct GroupedSequenceIterator: IteratorProtocol { + private var base: GroupedSequence + + private var keyIterator: EnumeratedSequence>.Iterator + + init(base: GroupedSequence) { + self.base = base + keyIterator = base.keys.enumerated().makeIterator() + } + + mutating func next() -> (Key, [Value])? { + if let (index, key) = keyIterator.next() { + let values = base.groupsForKeys[index] + return (key, values) + } else { + return nil + } + } +} diff --git a/Sources/Apollo/HTTPNetworkTransport.swift b/Sources/Apollo/HTTPNetworkTransport.swift index 195432e538..1cfdc04ff9 100644 --- a/Sources/Apollo/HTTPNetworkTransport.swift +++ b/Sources/Apollo/HTTPNetworkTransport.swift @@ -47,6 +47,7 @@ public protocol HTTPNetworkTransportTaskCompletedDelegate: HTTPNetworkTransportD // MARK: - +/// Methods which will be called if an error is receieved at the network level. public protocol HTTPNetworkTransportRetryDelegate: HTTPNetworkTransportDelegate { /// Called when an error has been received after a request has been sent to the server to see if an operation should be retried or not. @@ -65,12 +66,19 @@ public protocol HTTPNetworkTransportRetryDelegate: HTTPNetworkTransportDelegate retryHandler: @escaping (_ shouldRetry: Bool) -> Void) } -/// Methods which will be called after some kind of response has been received and it contains GraphQLErrors -public protocol HTTPNetworkTransportGraphQLErrorDelegate: HTTPNetworkTransportDelegate { +// MARK: - +/// Methods which will be called after some kind of response has been received and it contains GraphQLErrors. +public protocol HTTPNetworkTransportGraphQLErrorDelegate: HTTPNetworkTransportDelegate { /// Called when response contains one or more GraphQL errors. - /// NOTE: Don't just call the `retryHandler` with `true` all the time, or you can potentially wind up in an infinite loop of errors + /// + /// NOTE: The mere presence of a GraphQL error does not necessarily mean a request failed! + /// GraphQL is design to allow partial success/failures to return, so make sure + /// you're validating the *type* of error you're getting in this before deciding whether to retry or not. + /// + /// ALSO NOTE: Don't just call the `retryHandler` with `true` all the time, or you can + /// potentially wind up in an infinite loop of errors /// /// - Parameters: /// - networkTransport: The network transport which received the error @@ -79,7 +87,6 @@ public protocol HTTPNetworkTransportGraphQLErrorDelegate: HTTPNetworkTransportDe func networkTransport(_ networkTransport: HTTPNetworkTransport, receivedGraphQLErrors errors: [GraphQLError], retryHandler: @escaping (_ shouldRetry: Bool) -> Void) } - // MARK: - /// A network transport that uses HTTP POST requests to send GraphQL operations to a server, and that uses `URLSession` as the networking implementation. @@ -224,9 +231,10 @@ public class HTTPNetworkTransport { files: [GraphQLFile]?, response: GraphQLResponse, completionHandler: @escaping (_ result: Result, Error>) -> Void) { - guard let delegate = self.delegate as? HTTPNetworkTransportGraphQLErrorDelegate, + guard + let delegate = self.delegate as? HTTPNetworkTransportGraphQLErrorDelegate, let graphQLErrors = response.parseErrorsOnlyFast(), - !graphQLErrors.isEmpty else { + graphQLErrors.isNotEmpty else { completionHandler(.success(response)) return } @@ -379,16 +387,18 @@ public class HTTPNetworkTransport { } case .POST: do { - if let files = files, !files.isEmpty { - let formData = try requestCreator.requestMultipartFormData( - for: operation, - files: files, - sendOperationIdentifiers: self.sendOperationIdentifiers, - serializationFormat: self.serializationFormat, - manualBoundary: nil) - - request.setValue("multipart/form-data; boundary=\(formData.boundary)", forHTTPHeaderField: "Content-Type") - request.httpBody = try formData.encode() + if + let files = files, + files.isNotEmpty { + let formData = try requestCreator.requestMultipartFormData( + for: operation, + files: files, + sendOperationIdentifiers: self.sendOperationIdentifiers, + serializationFormat: self.serializationFormat, + manualBoundary: nil) + + request.setValue("multipart/form-data; boundary=\(formData.boundary)", forHTTPHeaderField: "Content-Type") + request.httpBody = try formData.encode() } else { request.httpBody = try serializationFormat.serialize(value: body) } diff --git a/Sources/Apollo/Utilities.swift b/Sources/Apollo/HTTPURLResponse+Helpers.swift similarity index 82% rename from Sources/Apollo/Utilities.swift rename to Sources/Apollo/HTTPURLResponse+Helpers.swift index 4778c3e0d3..128e7f60ca 100644 --- a/Sources/Apollo/Utilities.swift +++ b/Sources/Apollo/HTTPURLResponse+Helpers.swift @@ -15,9 +15,3 @@ extension HTTPURLResponse { return String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingName as CFString))) } } - -public protocol Matchable { - associatedtype Base - static func ~=(pattern: Self, value: Base) -> Bool -} - diff --git a/Sources/Apollo/Matchable.swift b/Sources/Apollo/Matchable.swift new file mode 100644 index 0000000000..d09ca9653d --- /dev/null +++ b/Sources/Apollo/Matchable.swift @@ -0,0 +1,14 @@ +// +// Matchable.swift +// Apollo +// +// Created by Ellen Shapiro on 10/29/19. +// Copyright © 2019 Apollo GraphQL. All rights reserved. +// + +import Foundation + +public protocol Matchable { + associatedtype Base + static func ~=(pattern: Self, value: Base) -> Bool +} diff --git a/Sources/Apollo/Promise.swift b/Sources/Apollo/Promise.swift index c838279c94..ed778e033a 100644 --- a/Sources/Apollo/Promise.swift +++ b/Sources/Apollo/Promise.swift @@ -226,8 +226,10 @@ final class Promise { lock.withLock { // If the promise has been resolved and there are no existing result handlers, // there is no need to append the handler to the array first. - if case .resolved(let result) = state, resultHandlers.isEmpty { - handler(result) + if + case .resolved(let result) = state, + resultHandlers.isEmpty { + handler(result) } else { resultHandlers.append(handler) } diff --git a/Sources/Apollo/String+SHA.swift b/Sources/Apollo/String+SHA.swift index a1d3c5b930..27912773a1 100644 --- a/Sources/Apollo/String+SHA.swift +++ b/Sources/Apollo/String+SHA.swift @@ -1,11 +1,3 @@ -// -// String+SHA.swift -// Apollo -// -// Created by Ellen Shapiro on 9/18/19. -// Copyright © 2019 Apollo GraphQL. All rights reserved. -// - import Foundation import CommonCrypto