diff --git a/CHANGELOG.md b/CHANGELOG.md index 4607e3aa..f9e5dc30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 1.7.0 +- **Add**: `TINetworking` - Swagger-frendly networking layer helpers + ### 1.6.0 - **Add**: the pretty timer - TITimer. diff --git a/LeadKit.podspec b/LeadKit.podspec index 03d8ca8c..dc8548be 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "1.6.0" + s.version = "1.7.0" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" diff --git a/Package.resolved b/Package.resolved index 3dfc4c9c..94b92e20 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "Alamofire", + "repositoryURL": "https://github.com/Alamofire/Alamofire.git", + "state": { + "branch": null, + "revision": "f96b619bcb2383b43d898402283924b80e2c4bae", + "version": "5.4.3" + } + }, { "package": "Cursors", "repositoryURL": "https://github.com/petropavel13/Cursors", diff --git a/Package.swift b/Package.swift index 8f8862f1..03aebe37 100644 --- a/Package.swift +++ b/Package.swift @@ -17,6 +17,7 @@ let package = Package( .library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]), .library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]), .library(name: "TITableKitUtils", targets: ["TITableKitUtils"]), + .library(name: "TINetworking", targets: ["TINetworking"]), // MARK: - Elements .library(name: "OTPSwiftView", targets: ["OTPSwiftView"]), @@ -24,9 +25,10 @@ let package = Package( .library(name: "TIPagination", targets: ["TIPagination"]), ], dependencies: [ - .package(url: "https://github.com/maxsokolov/TableKit.git", from: "2.11.0"), - .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", from: "4.2.2"), - .package(url: "https://github.com/petropavel13/Cursors", from: "0.5.1") + .package(url: "https://github.com/maxsokolov/TableKit.git", .upToNextMajor(from: "2.11.0")), + .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.2.2")), + .package(url: "https://github.com/petropavel13/Cursors", .upToNextMajor(from: "0.5.1")), + .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.4.0")), ], targets: [ @@ -39,6 +41,7 @@ let package = Package( .target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils/Sources"), .target(name: "TIKeychainUtils", dependencies: ["TIFoundationUtils", "KeychainAccess"], path: "TIKeychainUtils/Sources"), .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), + .target(name: "TINetworking", dependencies: ["TISwiftUtils", "Alamofire"], path: "TINetworking/Sources"), // MARK: - Elements diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index 2079438d..557d461e 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '1.6.0' + s.version = '1.7.0' s.summary = 'Set of helpers for Foundation framework classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIKeychainUtils/TIKeychainUtils.podspec b/TIKeychainUtils/TIKeychainUtils.podspec index 407932f6..8e72b025 100644 --- a/TIKeychainUtils/TIKeychainUtils.podspec +++ b/TIKeychainUtils/TIKeychainUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIKeychainUtils' - s.version = '1.6.0' + s.version = '1.7.0' s.summary = 'Set of helpers for Keychain classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TINetworking/Sources/Mapping/BaseContent.swift b/TINetworking/Sources/Mapping/BaseContent.swift new file mode 100644 index 00000000..b91c5b6d --- /dev/null +++ b/TINetworking/Sources/Mapping/BaseContent.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class BaseContent: Content { + public let mediaTypeName: String + + public init(mediaTypeName: String = CommonMediaTypes.textPlain.rawValue) { + self.mediaTypeName = mediaTypeName + } +} diff --git a/TINetworking/Sources/Mapping/BodyContent/ApplicationJsonBodyContent.swift b/TINetworking/Sources/Mapping/BodyContent/ApplicationJsonBodyContent.swift new file mode 100644 index 00000000..a99145eb --- /dev/null +++ b/TINetworking/Sources/Mapping/BodyContent/ApplicationJsonBodyContent.swift @@ -0,0 +1,50 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +open class ApplicationJsonBodyContent: BaseContent, BodyContent { + private let encodingClosure: ThrowableResultClosure + + public init(body: Body, jsonEncoder: JSONEncoder = JSONEncoder()) where Body: Encodable { + encodingClosure = { + try jsonEncoder.encode(body) + } + + super.init(mediaTypeName: CommonMediaTypes.applicationJson.rawValue) + } + + public init(jsonBody: Body, options: JSONSerialization.WritingOptions = .prettyPrinted) { + encodingClosure = { + try JSONSerialization.data(withJSONObject: jsonBody, options: options) + } + + super.init(mediaTypeName: CommonMediaTypes.applicationJson.rawValue) + } + + // MARK: - BodyContent + + public func encodeBody() throws -> Data { + try encodingClosure() + } +} diff --git a/TINetworking/Sources/Mapping/BodyContent/BodyContent.swift b/TINetworking/Sources/Mapping/BodyContent/BodyContent.swift new file mode 100644 index 00000000..e055fa9a --- /dev/null +++ b/TINetworking/Sources/Mapping/BodyContent/BodyContent.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol BodyContent: Content { + func encodeBody() throws -> Data +} diff --git a/TINetworking/Sources/Mapping/BodyContent/EmptyBodyContent.swift b/TINetworking/Sources/Mapping/BodyContent/EmptyBodyContent.swift new file mode 100644 index 00000000..faed4a9c --- /dev/null +++ b/TINetworking/Sources/Mapping/BodyContent/EmptyBodyContent.swift @@ -0,0 +1,38 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public final class EmptyBodyContent: BaseContent, BodyContent { + + // MARK: - BodyContent + + public func encodeBody() throws -> Data { + Data() + } +} + +public extension BodyContent { + static var empty: EmptyBodyContent { + .init() + } +} diff --git a/TINetworking/Sources/Mapping/Codable+Required.swift b/TINetworking/Sources/Mapping/Codable+Required.swift new file mode 100644 index 00000000..634f7d6c --- /dev/null +++ b/TINetworking/Sources/Mapping/Codable+Required.swift @@ -0,0 +1,45 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public extension KeyedDecodingContainer { + func decode(_ type: T?.Type, + forKey key: Key, + required: Bool) throws -> T? { + + required + ? try decode(type, forKey: key) + : try decodeIfPresent(T.self, forKey: key) + } +} + +public extension KeyedEncodingContainer { + mutating func encode(_ value: T?, + forKey key: Key, + required: Bool) throws { + + if let value = value { + try encode(value, forKey: key) + } else if required { + try encodeNil(forKey: key) + } + } +} diff --git a/TINetworking/Sources/Mapping/CommonMediaTypes.swift b/TINetworking/Sources/Mapping/CommonMediaTypes.swift new file mode 100644 index 00000000..b925af5f --- /dev/null +++ b/TINetworking/Sources/Mapping/CommonMediaTypes.swift @@ -0,0 +1,26 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public enum CommonMediaTypes: String { + case applicationJson = "application/json" + case textPlain = "text/plain" +} diff --git a/TINetworking/Sources/Mapping/Content.swift b/TINetworking/Sources/Mapping/Content.swift new file mode 100644 index 00000000..b5735b3b --- /dev/null +++ b/TINetworking/Sources/Mapping/Content.swift @@ -0,0 +1,25 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol Content { + var mediaTypeName: String { get } +} diff --git a/TINetworking/Sources/Mapping/OneOfMapping/AnyTypeMapping.swift b/TINetworking/Sources/Mapping/OneOfMapping/AnyTypeMapping.swift new file mode 100644 index 00000000..2e92361e --- /dev/null +++ b/TINetworking/Sources/Mapping/OneOfMapping/AnyTypeMapping.swift @@ -0,0 +1,47 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TISwiftUtils + +public struct AnyTypeMapping { + private let mappingClosure: ResultClosure> + + public let type: Any.Type + + public init(decoder: Decoder, + transform: @escaping Closure) { + + type = T.self + + mappingClosure = { + do { + return .success(transform(try T(from: decoder))) + } catch { + return .failure(error) + } + } + } + + public func decode() -> Result { + mappingClosure() + } +} diff --git a/TINetworking/Sources/Mapping/OneOfMapping/OneOfMappingError.swift b/TINetworking/Sources/Mapping/OneOfMapping/OneOfMappingError.swift new file mode 100644 index 00000000..501adc40 --- /dev/null +++ b/TINetworking/Sources/Mapping/OneOfMapping/OneOfMappingError.swift @@ -0,0 +1,43 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct OneOfMappingError: Error, CustomDebugStringConvertible { + public typealias MappingFailures = [KeyValueTuple] + + public let codingPath: [CodingKey] + public let mappingFailures: MappingFailures + + public init(codingPath: [CodingKey], mappingFailures: MappingFailures) { + self.codingPath = codingPath + self.mappingFailures = mappingFailures + } + + public var debugDescription: String { + var formattedString = "\"oneOf\" mapping failed for codingPath \(codingPath)\nwith following errors:\n" + + for (type, error) in mappingFailures { + formattedString += "\(type) mapping failed with error: \(error)\n" + } + + return formattedString + } +} diff --git a/TINetworking/Sources/Mapping/ResponseContent/ApplicationJsonResponseContent.swift b/TINetworking/Sources/Mapping/ResponseContent/ApplicationJsonResponseContent.swift new file mode 100644 index 00000000..37a6497f --- /dev/null +++ b/TINetworking/Sources/Mapping/ResponseContent/ApplicationJsonResponseContent.swift @@ -0,0 +1,45 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class ApplicationJsonResponseContent: BaseContent, ResponseContent { + public let jsonDecoder: JSONDecoder + + public init(jsonDecoder: JSONDecoder = JSONDecoder()) { + self.jsonDecoder = jsonDecoder + + super.init(mediaTypeName: CommonMediaTypes.applicationJson.rawValue) + } + + // MARK: - ResponseContent + + public func decodeResponse(data: Data) throws -> Model { + try jsonDecoder.decode(Model.self, from: data) + } +} + +public extension JSONDecoder { + func responseContent() -> ApplicationJsonResponseContent { + .init(jsonDecoder: self) + } +} diff --git a/TINetworking/Sources/Mapping/ResponseContent/EmptyResponseContent.swift b/TINetworking/Sources/Mapping/ResponseContent/EmptyResponseContent.swift new file mode 100644 index 00000000..32aaec1e --- /dev/null +++ b/TINetworking/Sources/Mapping/ResponseContent/EmptyResponseContent.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public final class EmptyResponseContent: BaseContent, ResponseContent { + public func decodeResponse(data: Data) throws -> Void { + () + } +} diff --git a/TINetworking/Sources/Mapping/ResponseContent/MapResponseContent.swift b/TINetworking/Sources/Mapping/ResponseContent/MapResponseContent.swift new file mode 100644 index 00000000..7e38fabb --- /dev/null +++ b/TINetworking/Sources/Mapping/ResponseContent/MapResponseContent.swift @@ -0,0 +1,60 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public final class MapResponseContent: BaseContent, ResponseContent { + private let decodeClosure: ThrowableClosure + + public init(responseContent: C, transform: @escaping Closure) { + decodeClosure = { + transform(try responseContent.decodeResponse(data: $0)) + } + + super.init(mediaTypeName: responseContent.mediaTypeName) + } + + // MARK: - ResponseContent + + public func decodeResponse(data: Data) throws -> Model { + try decodeClosure(data) + } +} + +public extension ResponseContent { + typealias TransformClosure = Closure + + func map(_ transform: @escaping TransformClosure) -> MapResponseContent { + .init(responseContent: self, transform: transform) + } +} + +public extension JSONDecoder { + func responseContent(_ tranfsorm: @escaping Closure) -> MapResponseContent { + responseContent().map(tranfsorm) + } + + func decoding(to tranfsorm: @escaping Closure) -> ThrowableClosure { + responseContent(tranfsorm).decodeResponse + } +} diff --git a/TINetworking/Sources/Mapping/ResponseContent/ResponseContent.swift b/TINetworking/Sources/Mapping/ResponseContent/ResponseContent.swift new file mode 100644 index 00000000..91cdd16c --- /dev/null +++ b/TINetworking/Sources/Mapping/ResponseContent/ResponseContent.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol ResponseContent: Content { + associatedtype Model + + func decodeResponse(data: Data) throws -> Model +} diff --git a/TINetworking/Sources/Mapping/ResponseContent/TextPlainResponseContent.swift b/TINetworking/Sources/Mapping/ResponseContent/TextPlainResponseContent.swift new file mode 100644 index 00000000..c26cd2ff --- /dev/null +++ b/TINetworking/Sources/Mapping/ResponseContent/TextPlainResponseContent.swift @@ -0,0 +1,48 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public final class TextPlainResponseContent: BaseContent, ResponseContent { + public struct StringDecodingError: Error { + let data: Data + let encoding: String.Encoding + } + + private let encoding: String.Encoding + + public init(encoding: String.Encoding = .utf8) { + self.encoding = encoding + + super.init(mediaTypeName: CommonMediaTypes.textPlain.rawValue) + } + + // MARK: - ResponseContent + + public func decodeResponse(data: Data) throws -> String { + guard let plainText = String(data: data, encoding: encoding) else { + throw StringDecodingError(data: data, encoding: encoding) + } + + return plainText + } +} diff --git a/TINetworking/Sources/Parameters/Encoding/BaseUrlParameterEncoding.swift b/TINetworking/Sources/Parameters/Encoding/BaseUrlParameterEncoding.swift new file mode 100644 index 00000000..90a9f75c --- /dev/null +++ b/TINetworking/Sources/Parameters/Encoding/BaseUrlParameterEncoding.swift @@ -0,0 +1,47 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Alamofire + +open class BaseUrlParameterEncoding { + private let encoding: URLEncoding = .queryString + + public init() {} + + open func encode(parameters: [String: Parameter]) -> [KeyValueTuple] { + var filteredComponents: [KeyValueTuple] = [] + + for key in parameters.keys.sorted(by: <) { + guard let parameter = parameters[key] else { + continue + } + + let components = encoding.queryComponents(fromKey: key, value: parameter.value) + // filter components with empty values if parameter doesn't allow empty value + .filter { !$0.1.isEmpty || parameter.allowEmptyValue } + + filteredComponents.append(contentsOf: components) + } + + return filteredComponents + } +} diff --git a/TINetworking/Sources/Parameters/Encoding/PathParameterEncoding.swift b/TINetworking/Sources/Parameters/Encoding/PathParameterEncoding.swift new file mode 100644 index 00000000..f11c3da5 --- /dev/null +++ b/TINetworking/Sources/Parameters/Encoding/PathParameterEncoding.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +open class PathParameterEncoding: BaseUrlParameterEncoding, ParameterEncoding { + public let templateUrl: String + + public init(templateUrl: String) { + self.templateUrl = templateUrl + } + + // MARK: - ParameterEncoding + + open func encode(parameters: [String: Parameter]) -> String { + .render(template: templateUrl, using: encode(parameters: parameters)) + } +} diff --git a/TINetworking/Sources/Parameters/Encoding/QueryStringParameterEncoding.swift b/TINetworking/Sources/Parameters/Encoding/QueryStringParameterEncoding.swift new file mode 100644 index 00000000..7931e26c --- /dev/null +++ b/TINetworking/Sources/Parameters/Encoding/QueryStringParameterEncoding.swift @@ -0,0 +1,34 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +open class QueryStringParameterEncoding: BaseUrlParameterEncoding, ParameterEncoding { + + // MARK: - ParameterEncoding + + open func encode(parameters: [String: Parameter]) -> [String: Any] { + let includedKeys = Set(super.encode(parameters: parameters).map { $0.key }) + + return parameters + .filter { includedKeys.contains($0.key) } + .mapValues { $0.value } + } +} diff --git a/TINetworking/Sources/Parameters/Parameter.swift b/TINetworking/Sources/Parameters/Parameter.swift new file mode 100644 index 00000000..a1dab8a5 --- /dev/null +++ b/TINetworking/Sources/Parameters/Parameter.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct Parameter { + public let value: Any + public let allowEmptyValue: Bool + + public init(value: Any, allowEmptyValue: Bool = false) { + self.value = value + self.allowEmptyValue = allowEmptyValue + } +} diff --git a/TINetworking/Sources/Parameters/ParameterEncoding.swift b/TINetworking/Sources/Parameters/ParameterEncoding.swift new file mode 100644 index 00000000..16f69eba --- /dev/null +++ b/TINetworking/Sources/Parameters/ParameterEncoding.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +protocol ParameterEncoding { + associatedtype Location: ParameterLocation + associatedtype Result + + func encode(parameters: [String: Parameter]) -> Result +} diff --git a/TINetworking/Sources/Parameters/ParameterLocation.swift b/TINetworking/Sources/Parameters/ParameterLocation.swift new file mode 100644 index 00000000..0fe68c32 --- /dev/null +++ b/TINetworking/Sources/Parameters/ParameterLocation.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol ParameterLocation {} + +public struct LocationQuery: ParameterLocation {} +public struct LocationPath: ParameterLocation {} +public struct LocationHeader: ParameterLocation {} +public struct LocationCookie: ParameterLocation {} diff --git a/TINetworking/Sources/Request/Request.swift b/TINetworking/Sources/Request/Request.swift new file mode 100644 index 00000000..d4b88b14 --- /dev/null +++ b/TINetworking/Sources/Request/Request.swift @@ -0,0 +1,64 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Alamofire + +public struct Request { + + public var templatePath: String + public var method: HTTPMethod + public var requestBodyContent: Content + public var queryParameters: [String: Parameter] + public var pathParameters: [String: Parameter] + public var headerParameters: HTTPHeaders? + public var cookieParameters: [String: Parameter] + public var acceptableStatusCodes: Set + public var customServer: Server? + public var customServerVariables: [KeyValueTuple] + + public var path: String { + PathParameterEncoding(templateUrl: templatePath).encode(parameters: pathParameters) + } + + public init(templatePath: String, + method: HTTPMethod, + requestBodyContent: Content, + queryParameters: [String: Parameter] = [:], + pathParameters: [String: Parameter] = [:], + headerParameters: HTTPHeaders? = nil, + cookieParameters: [String: Parameter] = [:], + acceptableStatusCodes: Set = [200], + customServer: Server? = nil, + customServerVariables: [KeyValueTuple] = []) { + + self.templatePath = templatePath + self.method = method + self.requestBodyContent = requestBodyContent + self.queryParameters = queryParameters + self.pathParameters = pathParameters + self.headerParameters = headerParameters + self.cookieParameters = cookieParameters + self.acceptableStatusCodes = acceptableStatusCodes + self.customServer = customServer + self.customServerVariables = customServerVariables + } +} diff --git a/TINetworking/Sources/Response/MimeTypeUnsupportedError.swift b/TINetworking/Sources/Response/MimeTypeUnsupportedError.swift new file mode 100644 index 00000000..a447c7ef --- /dev/null +++ b/TINetworking/Sources/Response/MimeTypeUnsupportedError.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +open class MimeTypeUnsupportedError: Error, CustomDebugStringConvertible { + + public let mimeType: String? + + // MARK: - CustomDebugStringConvertible + + public var debugDescription: String { + "Mime type \(mimeType.debugDescription) isn't supported." + } + + public init(mimeType: String?) { + self.mimeType = mimeType + } +} diff --git a/TINetworking/Sources/Response/ResponseType+Decoding.swift b/TINetworking/Sources/Response/ResponseType+Decoding.swift new file mode 100644 index 00000000..ad2a0499 --- /dev/null +++ b/TINetworking/Sources/Response/ResponseType+Decoding.swift @@ -0,0 +1,58 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public typealias StatusCodeMimeType = (statusCode: Int, mimeType: String?) +public typealias StatusCodesMimeType = (statusCodes: Set, mimeType: String?) + +public typealias DecodingClosure = ThrowableClosure + +public extension ResponseType { + func decode(mapping: [KeyValueTuple>]) -> Result { + for ((mappingStatusCode, mappingMimeType), decodeClosure) in mapping + where mappingStatusCode == statusCode && mappingMimeType == mimeType { + do { + return .success(try decodeClosure(data)) + } catch { + return .failure(objectMappingError(underlyingError: error)) + } + } + + guard mapping.contains(where: { $0.key.statusCode == statusCode }) else { + return .failure(unsupportedStatusCodeError(statusCode: statusCode)) + } + + guard mapping.contains(where: { $0.key.mimeType == mimeType }) else { + return .failure(unsupportedMimeTypeError(mimeType: mimeType)) + } + + return .failure(unsupportedStatusCodeMimeTypePairError(statusCode: statusCode, mimeType: mimeType)) + } + + func decode(mapping: [KeyValueTuple>]) -> Result { + decode(mapping: mapping.map { key, value in + key.statusCodes.map { KeyValueTuple(StatusCodeMimeType($0, key.mimeType), value) } + }.flatMap { $0 }) + } +} diff --git a/TINetworking/Sources/Response/ResponseType.swift b/TINetworking/Sources/Response/ResponseType.swift new file mode 100644 index 00000000..d6980445 --- /dev/null +++ b/TINetworking/Sources/Response/ResponseType.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol ResponseType { + associatedtype ErrorType: Error + + var statusCode: Int { get } + var data: Data { get } + var mimeType: String? { get } + + func unsupportedStatusCodeError(statusCode: Int) -> ErrorType + func unsupportedMimeTypeError(mimeType: String?) -> ErrorType + func objectMappingError(underlyingError: Error) -> ErrorType + func unsupportedStatusCodeMimeTypePairError(statusCode: Int, mimeType: String?) -> ErrorType +} diff --git a/TINetworking/Sources/Response/StatusCodeMimeTypePairUnsupportedError.swift b/TINetworking/Sources/Response/StatusCodeMimeTypePairUnsupportedError.swift new file mode 100644 index 00000000..844e9044 --- /dev/null +++ b/TINetworking/Sources/Response/StatusCodeMimeTypePairUnsupportedError.swift @@ -0,0 +1,38 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +open class StatusCodeMimeTypePairUnsupportedError: MimeTypeUnsupportedError { + + public let statusCode: Int + + // MARK: - CustomDebugStringConvertible + + public override var debugDescription: String { + "Status code: \(statusCode), mimeType: \(mimeType ?? "nil") pair is unsupported!" + } + + public init(statusCode: Int, mimeType: String?) { + self.statusCode = statusCode + + super.init(mimeType: mimeType) + } +} diff --git a/TINetworking/Sources/Server.swift b/TINetworking/Sources/Server.swift new file mode 100644 index 00000000..e05db673 --- /dev/null +++ b/TINetworking/Sources/Server.swift @@ -0,0 +1,81 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +private enum Scheme: String { + case https + + var urlPrefix: String { + rawValue + "://" + } +} + +public struct Server { + public struct Variable { + public let values: [String] + public let defaultValue: String + + public init(values: [String], defaultValue: String) { + self.values = values + self.defaultValue = defaultValue + } + } + + private var defaultVariables: [KeyValueTuple] { + variables.map { ($0.key, $0.value.defaultValue) } + } + + public let urlTemplate: String + public let variables: [String: Variable] + + public init(urlTemplate: String, variables: [String: Variable]) { + self.urlTemplate = urlTemplate + self.variables = variables + } + + public init(baseUrl: String) { + self.init(urlTemplate: baseUrl, variables: [:]) + } + + public func url(using variables: [KeyValueTuple] = [], + appendHttpsSchemeIfMissing: Bool = true) -> URL? { + + guard !variables.isEmpty else { + return URL(string: .render(template: urlTemplate, using: defaultVariables)) + } + + let defaultVariablesToApply = self.defaultVariables + .filter { (key, _) in variables.contains { $0.key == key } } + + let defaultParametersTemplate = String.render(template: urlTemplate, + using: defaultVariablesToApply) + + let formattedUrlString = String.render(template: defaultParametersTemplate, using: variables) + + if appendHttpsSchemeIfMissing, !formattedUrlString.contains(Scheme.https.urlPrefix) { + return URL(string: Scheme.https.urlPrefix + formattedUrlString) + } + + return URL(string: formattedUrlString) + } +} diff --git a/TINetworking/Sources/SessionFactory.swift b/TINetworking/Sources/SessionFactory.swift new file mode 100644 index 00000000..d12aff10 --- /dev/null +++ b/TINetworking/Sources/SessionFactory.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import Alamofire + +open class SessionFactory { + + public var timeoutInterval: TimeInterval + public var additionalHttpHeaders: HTTPHeaders + public var serverTrustPolicies: [String: ServerTrustEvaluating] + + public init(timeoutInterval: TimeInterval = 20, + additionalHttpHeaders: HTTPHeaders = HTTPHeaders(), + trustPolicies: [String: ServerTrustEvaluating] = [:]) { + + self.timeoutInterval = timeoutInterval + self.additionalHttpHeaders = additionalHttpHeaders + self.serverTrustPolicies = Dictionary(uniqueKeysWithValues: trustPolicies.map { ($0.key.urlHost, $0.value) }) + } + + open func createSession() -> Session { + Session(configuration: createSessionConfiguration(), + serverTrustManager: ServerTrustManager(allHostsMustBeEvaluated: false, + evaluators: serverTrustPolicies)) + } + + open func createSessionConfiguration() -> URLSessionConfiguration { + let sessionConfiguration = URLSessionConfiguration.default + sessionConfiguration.timeoutIntervalForResource = timeoutInterval + sessionConfiguration.httpAdditionalHeaders = additionalHttpHeaders.dictionary + + return sessionConfiguration + } +} diff --git a/TINetworking/Sources/String+URLExtensions.swift b/TINetworking/Sources/String+URLExtensions.swift new file mode 100644 index 00000000..e152ba5a --- /dev/null +++ b/TINetworking/Sources/String+URLExtensions.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public extension String { + var urlEscaped: String { + return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? self + } + + var urlHost: String { + URL(string: self)?.host ?? self + } + + static func render(template: String, using variables: [KeyValueTuple]) -> String { + variables.reduce(template) { + $0.replacingOccurrences(of: "{\($1.key)}", with: $1.value.urlEscaped) + } + } +} diff --git a/TINetworking/Sources/Typealiases.swift b/TINetworking/Sources/Typealiases.swift new file mode 100644 index 00000000..6adf2891 --- /dev/null +++ b/TINetworking/Sources/Typealiases.swift @@ -0,0 +1,23 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public typealias KeyValueTuple = (key: K, value: V) diff --git a/TINetworking/TINetworking.podspec b/TINetworking/TINetworking.podspec new file mode 100644 index 00000000..a9355971 --- /dev/null +++ b/TINetworking/TINetworking.podspec @@ -0,0 +1,16 @@ +Pod::Spec.new do |s| + s.name = 'TINetworking' + s.version = '1.7.0' + s.summary = 'Swagger-frendly networking layer helpers.' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '10.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' + + s.dependency 'Alamofire' +end diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index e3fbf696..b89b2c68 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '1.6.0' + s.version = '1.7.0' s.summary = 'Bunch of useful helpers for Swift development.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index c62ddc09..1a3d5eb1 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '1.6.0' + s.version = '1.7.0' s.summary = 'Set of helpers for TableKit classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITransitions/TITransitions.podspec b/TITransitions/TITransitions.podspec index 0f4cc5e2..af42244b 100644 --- a/TITransitions/TITransitions.podspec +++ b/TITransitions/TITransitions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITransitions' - s.version = '1.6.0' + s.version = '1.7.0' s.summary = 'Set of custom transitions to present controller. ' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index 759d9fcc..dc40a6f3 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '1.6.0' + s.version = '1.7.0' s.summary = 'Bunch of useful protocols and views.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index 0a863b71..88340b88 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '1.6.0' + s.version = '1.7.0' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' }