├── Tests ├── ServiceManagerTests │ ├── ServiceManagerTests.swift │ └── XCTestManifests.swift └── LinuxMain.swift ├── README.md ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── ServiceManager.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── Sources └── ServiceManager │ ├── Service │ ├── Info.plist │ ├── BBServiceKit.swift │ ├── ServiceManager.h │ └── Service.swift │ ├── Encoding │ ├── AnyEncoder.swift │ ├── EncodableExtensions.swift │ ├── PropertyListEncoding.swift │ ├── HTTPEncoding.swift │ ├── JSONEncoding.swift │ ├── URLEncoding.swift │ └── FormDataEncoding.swift │ ├── Route │ ├── ResponseComponent.swift │ ├── Route.swift │ ├── RequestComponent.swift │ └── URLComponent.swift │ ├── HTTP │ ├── HTTPScheme.swift │ ├── HTTPMethod.swift │ ├── HTTPError.swift │ └── HTTPHeader.swift │ ├── Protocols │ ├── URLRequestConvertible.swift │ ├── DataConvertible.swift │ └── URLConvertible.swift │ └── Decoding │ └── AnyDecoder.swift ├── ServiceManager.podspec ├── LICENSE ├── Package.swift └── .gitignore /Tests/ServiceManagerTests/ServiceManagerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import ServiceManager 3 | 4 | final class ServiceManagerTests: XCTestCase { } 5 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import ServiceManagerTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += ServiceManagerTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServiceManager 2 | 3 | Lightweight, Enumerated and Protocol Oriented Networking Module written in swift 5.0. ServiceManager helps reduce networking code in view controllers and presenters. 4 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ServiceManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /Tests/ServiceManagerTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(ServiceManagerTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /ServiceManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Service/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /ServiceManager.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | 3 | spec.name = "ServiceManager" 4 | spec.version = "1.1.0" 5 | spec.summary = "Lightweight, Enumerated and Protocol Oriented Networking Module written in swift 5.0." 6 | 7 | spec.description = "Lightweight, Enumerated and Protocol Oriented Networking Module written in swift 5.0. ServiceManager helps reduce networking code in view controllers and presenters." 8 | 9 | spec.homepage = "https://github.com/bibinjacobpulickal/ServiceManager" 10 | 11 | spec.license = "MIT" 12 | 13 | spec.author = { "Bibin Jacob Pulickal" => "bibinjacob123@gmail.com" } 14 | spec.social_media_url = "https://github.com/bibinjacobpulickal" 15 | 16 | spec.ios.deployment_target = '9.0' 17 | spec.osx.deployment_target = '10.11' 18 | 19 | spec.source = { :git => "https://github.com/bibinjacobpulickal/ServiceManager.git", :tag => "1.1.0" } 20 | 21 | spec.source_files = "ServiceManager/**/*.{h,m,swift}" 22 | 23 | spec.swift_version = ['3.0', '4.0', '4.2', '5.0'] 24 | 25 | end 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 bibinjacobpulickal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ServiceManager", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "ServiceManager", 12 | targets: ["ServiceManager"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "ServiceManager", 23 | dependencies: []), 24 | .testTarget( 25 | name: "ServiceManagerTests", 26 | dependencies: ["ServiceManager"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Encoding/AnyEncoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnyEncoder.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol AnyEncoder { 28 | func encode(_ value: T) throws -> Data 29 | } 30 | 31 | extension JSONEncoder: AnyEncoder { } 32 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Service/BBServiceKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BBServiceKit.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Reference to `Service.shared` for quick bootstrapping and examples. 28 | public let BB = Service.shared 29 | 30 | /// Current BBServiceKit version. Necessary since SPM doesn't use dynamic libraries. Plus this will be more accurate. 31 | let version = "1.2.0" 32 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Route/ResponseComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseComponent.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol ResponseComponent { 28 | 29 | // URLSession response object decoder, defaults to JSONDecoder(). 30 | var decoder: AnyDecoder { get } 31 | } 32 | 33 | public extension ResponseComponent { 34 | 35 | var decoder: AnyDecoder { JSONDecoder() } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ServiceManager/HTTP/HTTPScheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPScheme.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public struct HTTPScheme { 28 | /// `HTTPS` scheme. 29 | public static let https = HTTPScheme(rawValue: "https") 30 | /// `HTTP` scheme. 31 | public static let http = HTTPScheme(rawValue: "http") 32 | /// `FTP` scheme. 33 | public static let ftp = HTTPScheme(rawValue: "ftp") 34 | 35 | public let rawValue: String 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Route/Route.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Route.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol Route: URLComponent, RequestComponent, ResponseComponent { } 28 | 29 | public extension Route { 30 | 31 | func asURLRequest() throws -> URLRequest { 32 | let url = try asURL() 33 | let request = try URLRequest(url: url, method: method, body: body, headers: headers) 34 | return try encoding.encode(request, with: object, using: encoder) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Service/ServiceManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceManager.h 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import 26 | 27 | //! Project version number for ServiceManager. 28 | FOUNDATION_EXPORT double ServiceManagerVersionNumber; 29 | 30 | //! Project version string for ServiceManager. 31 | FOUNDATION_EXPORT const unsigned char ServiceManagerVersionString[]; 32 | 33 | // In this header, you should import all the public headers of your framework using statements like #import 34 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Encoding/EncodableExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EncodableExtensions.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public extension Encodable { 28 | 29 | func encoded(using encoder: AnyEncoder = JSONEncoder()) throws -> Data { 30 | try encoder.encode(self) 31 | } 32 | 33 | func jsonObject(using encoder: AnyEncoder = JSONEncoder()) throws -> Any { 34 | try JSONSerialization.jsonObject(with: try encoder.encode(self), 35 | options: .mutableLeaves) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Protocols/URLRequestConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLRequestConvertible.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Types adopting the `URLRequestConvertible` protocol can be used to safely construct `URLRequest`s. 28 | public protocol URLRequestConvertible { 29 | /// Returns a `URLRequest` or throws if an `Error` was encountered. 30 | /// 31 | /// - Returns: A `URLRequest`. 32 | /// - Throws: Any error thrown while constructing the `URLRequest`. 33 | func asURLRequest() throws -> URLRequest 34 | } 35 | 36 | extension URLRequestConvertible { 37 | /// The `URLRequest` returned by discarding any `Error` encountered. 38 | public var urlRequest: URLRequest? { try? asURLRequest() } 39 | } 40 | 41 | extension URLRequest: URLRequestConvertible { 42 | /// Returns `self`. 43 | public func asURLRequest() throws -> URLRequest { self } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Protocols/DataConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataConvertible.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol DataConvertible { 28 | func asData() throws -> Data 29 | } 30 | 31 | extension Data: DataConvertible { 32 | public func asData() -> Data { self } 33 | } 34 | 35 | extension String: DataConvertible { 36 | public func asData() -> Data { Data(self.utf8) } 37 | } 38 | 39 | extension Dictionary: DataConvertible where Key: Any, Value: Any { 40 | public func asData() throws -> Data { 41 | try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) 42 | } 43 | } 44 | 45 | extension Array: DataConvertible where Element: Any { 46 | public func asData() throws -> Data { 47 | try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) 48 | } 49 | } 50 | 51 | extension Encodable where Self: DataConvertible { 52 | public func asData() throws -> Data { try encoded() } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Route/RequestComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestComponent.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol RequestComponent: URLRequestConvertible { 28 | 29 | // URLRequest method eg: GET, POST etc. Defaults to get. 30 | var method: HTTPMethod { get } 31 | 32 | // URLRequest httpBody of type DataConvertible. Defaults to nil. 33 | var body: DataConvertible? { get } 34 | 35 | // URLRequest headers eg: ["Authorization": "Bearer..."]. Defaults to nil. 36 | var headers: HTTPHeaders? { get } 37 | 38 | // URLRequest object. Defaults to nil 39 | var object: Encodable? { get } 40 | 41 | // URLRequest object encoder. Defaults to JSONEncoder(). 42 | var encoder: AnyEncoder { get } 43 | 44 | // URLRequest object encoding. Defaults to URLEncoding.default. 45 | var encoding: HTTPEncoding { get } 46 | } 47 | 48 | public extension RequestComponent { 49 | 50 | var method: HTTPMethod { .get } 51 | 52 | var body: DataConvertible? { nil } 53 | 54 | var headers: HTTPHeaders? { nil } 55 | 56 | var object: Encodable? { nil } 57 | 58 | var encoder: AnyEncoder { JSONEncoder() } 59 | 60 | var encoding: HTTPEncoding { URLEncoding.default } 61 | } 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | Packages/ 41 | Package.pins 42 | Package.resolved 43 | *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | .DS_Store -------------------------------------------------------------------------------- /Sources/ServiceManager/Decoding/AnyDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnyDecoder.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol AnyDecoder { 28 | func decode(_ type: T.Type, from data: Data) throws -> T 29 | } 30 | 31 | extension JSONDecoder: AnyDecoder { } 32 | 33 | public extension Data { 34 | 35 | func decoded(using decoder: AnyDecoder = JSONDecoder()) throws -> T { 36 | try decoder.decode(T.self, from: self) 37 | } 38 | 39 | var prettyPrintedString: String { 40 | if let object = try? JSONSerialization.jsonObject(with: self, options: .mutableLeaves), 41 | JSONSerialization.isValidJSONObject(object), 42 | let data = try? JSONSerialization.data(withJSONObject: object, options: .prettyPrinted) { 43 | return String(decoding: data, as: UTF8.self) 44 | } else { 45 | return String(decoding: self, as: UTF8.self) 46 | } 47 | } 48 | } 49 | 50 | public extension KeyedDecodingContainerProtocol { 51 | 52 | func decode(forKey key: Key) throws -> T { 53 | try decode(T.self, forKey: key) 54 | } 55 | 56 | func decode(forKey key: Key, default defaultExpression: @autoclosure () -> T) throws -> T { 57 | try decodeIfPresent(T.self, forKey: key) ?? defaultExpression() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/ServiceManager/HTTP/HTTPMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPMethod.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Type representing HTTP methods. Raw `String` value is stored and compared case-sensitively, so 28 | /// `HTTPMethod.get != HTTPMethod(rawValue: "get")`. 29 | /// 30 | /// See https://tools.ietf.org/html/rfc7231#section-4.3 31 | public struct HTTPMethod: RawRepresentable, Equatable, Hashable { 32 | /// `CONNECT` method. 33 | public static let connect = HTTPMethod(rawValue: "CONNECT") 34 | /// `DELETE` method. 35 | public static let delete = HTTPMethod(rawValue: "DELETE") 36 | /// `GET` method. 37 | public static let get = HTTPMethod(rawValue: "GET") 38 | /// `HEAD` method. 39 | public static let head = HTTPMethod(rawValue: "HEAD") 40 | /// `OPTIONS` method. 41 | public static let options = HTTPMethod(rawValue: "OPTIONS") 42 | /// `PATCH` method. 43 | public static let patch = HTTPMethod(rawValue: "PATCH") 44 | /// `POST` method. 45 | public static let post = HTTPMethod(rawValue: "POST") 46 | /// `PUT` method. 47 | public static let put = HTTPMethod(rawValue: "PUT") 48 | /// `TRACE` method. 49 | public static let trace = HTTPMethod(rawValue: "TRACE") 50 | 51 | public let rawValue: String 52 | 53 | public init(rawValue: String) { 54 | self.rawValue = rawValue 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Route/URLComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLComponent.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public protocol URLComponent: URLConvertible { 28 | 29 | // http, https etc, defaults to https. 30 | var scheme: HTTPScheme { get } 31 | 32 | // eg: www.google.com 33 | var host: String { get } 34 | 35 | // eg: 8080 36 | var port: Int? { get } 37 | 38 | // eg: /search 39 | var path: String { get } 40 | 41 | // eg: ["key": "item"], defaults to nil. 42 | var queries: Encodable? { get } 43 | } 44 | 45 | public extension URLComponent { 46 | 47 | var scheme: HTTPScheme { .https } 48 | 49 | var port: Int? { nil } 50 | 51 | var queries: Encodable? { nil } 52 | } 53 | 54 | public extension URLComponent { 55 | 56 | func asURL() throws -> URL { 57 | var components = URLComponents() 58 | components.scheme = scheme.rawValue 59 | components.host = host 60 | components.port = port 61 | components.path = path 62 | if let queries = try queries?.jsonObject() as? Parameters { 63 | for (key, value) in queries { 64 | let query = URLQueryItem(name: key, value: "\(value)") 65 | if components.queryItems?.append(query) == nil { 66 | components.queryItems = [query] 67 | } 68 | } 69 | } 70 | return try components.asURL() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Protocols/URLConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLConvertible.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Types adopting the `URLConvertible` protocol can be used to construct `URL`s, which can then be used to construct 28 | /// `URLRequests`. 29 | public protocol URLConvertible { 30 | /// Returns a `URL` from the conforming instance or throws. 31 | /// 32 | /// - Returns: The `URL` created from the instance. 33 | /// - Throws: Any error thrown while creating the `URL`. 34 | func asURL() throws -> URL 35 | } 36 | 37 | extension String: URLConvertible { 38 | /// Returns a `URL` if `self` can be used to initialize a `URL` instance, otherwise throws. 39 | /// 40 | /// - Returns: The `URL` initialized with `self`. 41 | /// - Throws: An `HTTPError.invalidURL` instance. 42 | public func asURL() throws -> URL { 43 | guard let url = URL(string: self) else { throw HTTPError.invalidURL(url: self) } 44 | 45 | return url 46 | } 47 | } 48 | 49 | extension URL: URLConvertible { 50 | /// Returns `self`. 51 | public func asURL() throws -> URL { self } 52 | } 53 | 54 | extension URLComponents: URLConvertible { 55 | /// Returns a `URL` if the `self`'s `url` is not nil, otherwise throws. 56 | /// 57 | /// - Returns: The `URL` from the `url` property. 58 | /// - Throws: An `HTTPError.invalidURL` instance. 59 | public func asURL() throws -> URL { 60 | guard let url = url else { throw HTTPError.invalidURL(url: self) } 61 | 62 | return url 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Encoding/PropertyListEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PropertyListEncoding.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public struct PropertyListEncoding: ParameterEncoding { 28 | 29 | public static var `default`: PropertyListEncoding { PropertyListEncoding() } 30 | 31 | public static var xml: PropertyListEncoding { PropertyListEncoding(format: .xml) } 32 | 33 | public static var binary: PropertyListEncoding { PropertyListEncoding(format: .binary) } 34 | 35 | public static var openStep: PropertyListEncoding { PropertyListEncoding(format: .openStep) } 36 | 37 | public let format: PropertyListSerialization.PropertyListFormat 38 | 39 | public let options: PropertyListSerialization.WriteOptions 40 | 41 | public init( 42 | format: PropertyListSerialization.PropertyListFormat = .xml, 43 | options: PropertyListSerialization.WriteOptions = 0) 44 | { 45 | self.format = format 46 | self.options = options 47 | } 48 | 49 | public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { 50 | var urlRequest = try urlRequest.asURLRequest() 51 | 52 | guard let parameters = parameters else { return urlRequest } 53 | 54 | do { 55 | let data = try PropertyListSerialization.data( 56 | fromPropertyList: parameters, 57 | format: format, 58 | options: options 59 | ) 60 | 61 | if urlRequest.headers["Content-Type"] == nil { 62 | urlRequest.headers.update(.contentType("application/x-plist")) 63 | } 64 | 65 | urlRequest.httpBody = data 66 | } catch { 67 | throw HTTPError.parameterEncodingFailed(reason: .propertyListEncodingFailed(error: error)) 68 | } 69 | 70 | return urlRequest 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Encoding/HTTPEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPEncoding.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// A dictionary of parameters to apply to a `URLRequest`. 28 | public typealias Parameters = [String: Any] 29 | 30 | /// A type used to define how a set of parameters are applied to a `URLRequest`. 31 | public protocol HTTPEncoding { 32 | /// Creates a `URLRequest` by encoding parameters and applying them on the passed request. 33 | /// 34 | /// - Parameters: 35 | /// - urlRequest: `URLRequestConvertible` value onto which parameters will be encoded. 36 | /// - object: `object` to encode onto the request. 37 | /// - encoder: `encoder` to be used for encoding. 38 | /// 39 | /// - Returns: The encoded `URLRequest`. 40 | /// - Throws: Any `Error` produced during parameter encoding. 41 | func encode(_ urlRequest: URLRequestConvertible, with object: Encodable?, using encoder: AnyEncoder) throws -> URLRequest 42 | } 43 | 44 | /// A type used to define how a set of parameters are applied to a `URLRequest`. 45 | public protocol ParameterEncoding: HTTPEncoding { 46 | /// Creates a `URLRequest` by encoding parameters and applying them on the passed request. 47 | /// 48 | /// - Parameters: 49 | /// - urlRequest: `URLRequestConvertible` value onto which parameters will be encoded. 50 | /// - parameters: `Parameters` to encode onto the request. 51 | /// 52 | /// - Returns: The encoded `URLRequest`. 53 | /// - Throws: Any `Error` produced during parameter encoding. 54 | func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest 55 | } 56 | 57 | extension ParameterEncoding { 58 | 59 | public func encode(_ urlRequest: URLRequestConvertible, with object: Encodable?, using encoder: AnyEncoder) throws -> URLRequest { 60 | try encode(urlRequest, with: try object?.jsonObject(using: encoder) as? Parameters) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Encoding/JSONEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONEncoding.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public struct JSONEncoding: HTTPEncoding { 28 | 29 | public static var `default`: JSONEncoding { JSONEncoding() } 30 | 31 | public static var empty: JSONEncoding { JSONEncoding(options: []) } 32 | 33 | public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) } 34 | 35 | public static var fragmentsAllowed: JSONEncoding { JSONEncoding(options: .fragmentsAllowed) } 36 | 37 | @available(iOS 11.0, *) 38 | @available(OSX 10.13, *) 39 | public static var sortedKeys: JSONEncoding { JSONEncoding(options: .sortedKeys) } 40 | 41 | @available(iOS 13.0, *) 42 | @available(OSX 10.15, *) 43 | public static var withoutEscapingSlashes: JSONEncoding { JSONEncoding(options: .withoutEscapingSlashes) } 44 | 45 | public let options: JSONSerialization.WritingOptions? 46 | 47 | public init(options: JSONSerialization.WritingOptions? = nil) { 48 | self.options = options 49 | } 50 | 51 | public func encode(_ urlRequest: URLRequestConvertible, with object: Encodable?, using encoder: AnyEncoder) throws -> URLRequest { 52 | var urlRequest = try urlRequest.asURLRequest() 53 | 54 | guard let object = object else { return urlRequest } 55 | 56 | do { 57 | if let options = options { 58 | 59 | let jsonObject = try object.jsonObject(using: encoder) 60 | 61 | urlRequest.httpBody = try JSONSerialization.data(withJSONObject: jsonObject, options: options) 62 | } else { 63 | 64 | urlRequest.httpBody = try object.encoded(using: encoder) 65 | } 66 | } catch { 67 | throw HTTPError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) 68 | } 69 | if urlRequest.headers["Content-Type"] == nil { 70 | urlRequest.headers.update(.contentType("application/json")) 71 | } 72 | 73 | return urlRequest 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Encoding/URLEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLEncoding.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public struct URLEncoding: ParameterEncoding { 28 | 29 | public enum Destination { 30 | case methodDependent, queryString, httpBody 31 | } 32 | 33 | public enum ArrayEncoding { 34 | case brackets, noBrackets 35 | 36 | func encode(key: String) -> String { 37 | switch self { 38 | case .brackets: 39 | return "\(key)[]" 40 | case .noBrackets: 41 | return key 42 | } 43 | } 44 | } 45 | 46 | public enum BoolEncoding { 47 | case numeric, literal 48 | 49 | func encode(value: Bool) -> String { 50 | switch self { 51 | case .numeric: 52 | return value ? "1" : "0" 53 | case .literal: 54 | return value ? "true" : "false" 55 | } 56 | } 57 | } 58 | 59 | public static var `default`: URLEncoding { URLEncoding() } 60 | 61 | public static var methodDependent: URLEncoding { URLEncoding() } 62 | 63 | public static var queryString: URLEncoding { URLEncoding(destination: .queryString) } 64 | 65 | public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) } 66 | 67 | public let destination: Destination 68 | 69 | public let arrayEncoding: ArrayEncoding 70 | 71 | public let boolEncoding: BoolEncoding 72 | 73 | public init(destination: Destination = .methodDependent, arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric) { 74 | self.destination = destination 75 | self.arrayEncoding = arrayEncoding 76 | self.boolEncoding = boolEncoding 77 | } 78 | 79 | public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { 80 | var urlRequest = try urlRequest.asURLRequest() 81 | 82 | guard let parameters = parameters else { return urlRequest } 83 | 84 | let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET") 85 | if encodesParametersInURL(with: method) { 86 | guard let url = urlRequest.url else { 87 | throw HTTPError.parameterEncodingFailed(reason: .missingURL) 88 | } 89 | 90 | if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { 91 | let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) 92 | urlComponents.percentEncodedQuery = percentEncodedQuery 93 | urlRequest.url = urlComponents.url 94 | } 95 | } else { 96 | if urlRequest.headers["Content-Type"] == nil { 97 | urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8")) 98 | } 99 | 100 | urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false) 101 | } 102 | 103 | return urlRequest 104 | } 105 | 106 | public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { 107 | var components: [(String, String)] = [] 108 | 109 | if let dictionary = value as? [String: Any] { 110 | for (nestedKey, value) in dictionary { 111 | components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) 112 | } 113 | } else if let array = value as? [Any] { 114 | for value in array { 115 | components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value) 116 | } 117 | } else if let value = value as? NSNumber { 118 | if value.isBool { 119 | components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue)))) 120 | } else { 121 | components.append((escape(key), escape("\(value)"))) 122 | } 123 | } else if let bool = value as? Bool { 124 | components.append((escape(key), escape(boolEncoding.encode(value: bool)))) 125 | } else { 126 | components.append((escape(key), escape("\(value)"))) 127 | } 128 | 129 | return components 130 | } 131 | 132 | public func escape(_ string: String) -> String { 133 | let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 134 | let subDelimitersToEncode = "!$&'()*+,;=" 135 | 136 | var allowedCharacterSet = CharacterSet.urlQueryAllowed 137 | allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") 138 | 139 | var escaped = "" 140 | 141 | if #available(iOS 8.3, *) { 142 | escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string 143 | } else { 144 | let batchSize = 50 145 | var index = string.startIndex 146 | 147 | while index != string.endIndex { 148 | let startIndex = index 149 | let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex 150 | let range = startIndex.. String { 164 | var components: [(String, String)] = [] 165 | 166 | for key in parameters.keys.sorted(by: <) { 167 | let value = parameters[key]! 168 | components += queryComponents(fromKey: key, value: value) 169 | } 170 | return components.map { "\($0)=\($1)" }.joined(separator: "&") 171 | } 172 | 173 | private func encodesParametersInURL(with method: HTTPMethod) -> Bool { 174 | switch destination { 175 | case .queryString: 176 | return true 177 | case .httpBody: 178 | return false 179 | default: 180 | break 181 | } 182 | 183 | switch method { 184 | case .get, .head, .delete: 185 | return true 186 | default: 187 | return false 188 | } 189 | } 190 | } 191 | 192 | extension NSNumber { 193 | fileprivate var isBool: Bool { CFBooleanGetTypeID() == CFGetTypeID(self) } 194 | } 195 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Service/Service.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Service.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public typealias ServiceResult = Result 28 | public typealias ServiceCompletion = ((Result) -> Void)? 29 | 30 | public class Service { 31 | 32 | public static let shared = Service() 33 | 34 | public func result(_ route: Route, 35 | log: Bool = false, 36 | _ completion: ServiceCompletion = nil) { 37 | dataResult(route, log: log) { [weak self] result in 38 | self?.decodeDataResult(result, using: route.decoder, completion) 39 | } 40 | } 41 | 42 | public func result( 43 | _ url: URLConvertible, 44 | method: HTTPMethod = .get, 45 | body: DataConvertible? = nil, 46 | headers: HTTPHeaders? = nil, 47 | object: Encodable? = nil, 48 | encoder: AnyEncoder = JSONEncoder(), 49 | encoding: HTTPEncoding = JSONEncoding.default, 50 | decoder: AnyDecoder = JSONDecoder(), 51 | _ completion: ServiceCompletion = nil) { 52 | dataResult( 53 | url, 54 | method: method, 55 | body: body, 56 | headers: headers, 57 | object: object, 58 | encoder: encoder, 59 | encoding: encoding) { [weak self] result in 60 | self?.decodeDataResult(result, using: decoder, completion) 61 | } 62 | } 63 | 64 | private func decodeDataResult( 65 | _ result: Result, 66 | using decoder: AnyDecoder = JSONDecoder(), 67 | _ completion: ServiceCompletion = nil) { 68 | switch result { 69 | case .success(let data): 70 | do { 71 | completion?(.success(try data.decoded(using: decoder))) 72 | } catch { 73 | switch error { 74 | case DecodingError.keyNotFound(_, let context), 75 | DecodingError.valueNotFound(_, let context), 76 | DecodingError.typeMismatch(_, let context): 77 | print("Decoding Error:-") 78 | context.codingPath.forEach { codingKey in 79 | print("\tKey:\t\t\"\(codingKey.stringValue)\"") 80 | } 81 | print("\tDescription:", context.debugDescription) 82 | default: 83 | print(error.localizedDescription) 84 | } 85 | completion?(.failure(error)) 86 | } 87 | case .failure(let error): 88 | completion?(.failure(error)) 89 | } 90 | } 91 | 92 | public func dataResult( 93 | _ route: Route, 94 | log: Bool = false, 95 | _ completion: ServiceCompletion = nil) { 96 | do { 97 | let url = try route.asURL() 98 | let requestComponent = RequestConvertible( 99 | url: url, method: route.method, body: route.body, 100 | object: route.object, encoder: route.encoder, 101 | encoding: route.encoding, headers: route.headers) 102 | let request = try requestComponent.asURLRequest() 103 | dataTask(request, log: log) { (data, urlResponse, error) in 104 | if let error = error { 105 | completion?(.failure(error)) 106 | } else if let data = data { 107 | completion?(.success(data)) 108 | } 109 | } 110 | } catch { 111 | logSession(log: true, request: nil, response: nil, error: error, data: nil) 112 | completion?(.failure(error)) 113 | } 114 | } 115 | 116 | public func dataResult( 117 | _ url: URLConvertible, 118 | method: HTTPMethod = .get, 119 | body: DataConvertible? = nil, 120 | headers: HTTPHeaders? = nil, 121 | object: Encodable? = nil, 122 | encoder: AnyEncoder = JSONEncoder(), 123 | encoding: HTTPEncoding = JSONEncoding.default, 124 | _ completion: ServiceCompletion = nil) { 125 | do { 126 | let requestComponent = RequestConvertible( 127 | url: url, method: method, body: body, 128 | object: object, encoder: encoder, 129 | encoding: encoding, headers: headers) 130 | let request = try requestComponent.asURLRequest() 131 | dataTask(request, log: true) { (data, urlResponse, error) in 132 | if let error = error { 133 | completion?(.failure(error)) 134 | } else if let data = data { 135 | completion?(.success(data)) 136 | } 137 | } 138 | } catch { 139 | completion?(.failure(error)) 140 | } 141 | } 142 | 143 | public func dataTask( 144 | _ request: URLRequestConvertible, 145 | log: Bool = false, 146 | _ completion: ((Data?, HTTPURLResponse?, Error?) -> Void)? = nil) { 147 | do { 148 | let request = try request.asURLRequest() 149 | let task = URLSession.shared.dataTask(with: request) { [weak self] (data, urlResponse, error) in 150 | let httpUrlResponse = urlResponse as? HTTPURLResponse 151 | self?.logSession(log: log, request: request, response: httpUrlResponse, error: error, data: data) 152 | DispatchQueue.main.async { completion?(data, httpUrlResponse, error) } 153 | } 154 | task.resume() 155 | } catch { 156 | logSession(log: log, request: nil, response: nil, error: error, data: nil) 157 | completion?(nil, nil, error) 158 | } 159 | } 160 | 161 | func logSession(log: Bool, request: URLRequest?, response: HTTPURLResponse?, error: Error?, data: Data?) { 162 | 163 | if log == false && error == nil && (200..<300).contains(response?.statusCode ?? 0) { 164 | return 165 | } 166 | if request != nil { 167 | print("\(request?.httpMethod ?? "URL"):\t\t\(request?.url?.absoluteString ?? "Empty url string")") 168 | } 169 | if let headers = request?.allHTTPHeaderFields, !headers.isEmpty { 170 | print("Header:\t\t\(headers)") 171 | } 172 | if let data = request?.httpBody, !data.isEmpty { 173 | print("Body:\t\tSize: \(data)\n\(data.prettyPrintedString)") 174 | } 175 | if let statusCode = response?.statusCode { 176 | print("Status Code: \t\(statusCode)") 177 | } 178 | if let data = data, !data.isEmpty { 179 | print("Response:\t\tSize: \(data)\n\(data.prettyPrintedString)") 180 | } 181 | if let error = error { 182 | print("Error:\t\t\(error.localizedDescription)") 183 | } 184 | } 185 | 186 | struct RequestConvertible: URLRequestConvertible { 187 | let url: URLConvertible 188 | let method: HTTPMethod 189 | let body: DataConvertible? 190 | let object: Encodable? 191 | let encoder: AnyEncoder 192 | let encoding: HTTPEncoding 193 | let headers: HTTPHeaders? 194 | 195 | func asURLRequest() throws -> URLRequest { 196 | let request = try URLRequest(url: url, method: method, body: body, headers: headers) 197 | return try encoding.encode(request, with: object, using: encoder) 198 | } 199 | } 200 | } 201 | 202 | extension URLRequest { 203 | /// Creates an instance with the specified `url`, `method`, and `headers`. 204 | /// 205 | /// - Parameters: 206 | /// - url: The `URLConvertible` value. 207 | /// - method: The `HTTPMethod`. 208 | /// - headers: The `HTTPHeaders`, `nil` by default. 209 | /// - Throws: Any error thrown while converting the `URLConvertible` to a `URL`. 210 | public init(url: URLConvertible, method: HTTPMethod, body: DataConvertible?, headers: HTTPHeaders? = nil) throws { 211 | let url = try url.asURL() 212 | 213 | self.init(url: url) 214 | 215 | httpMethod = method.rawValue 216 | httpBody = try body?.asData() 217 | allHTTPHeaderFields = headers?.dictionary 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Sources/ServiceManager/HTTP/HTTPError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPError.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | public enum HTTPError: Error { 28 | 29 | public enum ParameterEncodingFailureReason { 30 | case missingURL 31 | case optionalDictionaryCastingFailed 32 | case jsonEncodingFailed(error: Error) 33 | case propertyListEncodingFailed(error: Error) 34 | } 35 | 36 | public enum MultipartEncodingFailureReason { 37 | case bodyPartURLInvalid(url: URL) 38 | case bodyPartFilenameInvalid(in: URL) 39 | case bodyPartFileNotReachable(at: URL) 40 | case bodyPartFileNotReachableWithError(atURL: URL, error: Error) 41 | case bodyPartFileIsDirectory(at: URL) 42 | case bodyPartFileSizeNotAvailable(at: URL) 43 | case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error) 44 | case bodyPartInputStreamCreationFailed(for: URL) 45 | 46 | case outputStreamCreationFailed(for: URL) 47 | case outputStreamFileAlreadyExists(at: URL) 48 | case outputStreamURLInvalid(url: URL) 49 | case outputStreamWriteFailed(error: Error) 50 | 51 | case inputStreamReadFailed(error: Error) 52 | } 53 | 54 | public enum ResponseValidationFailureReason { 55 | case dataFileNil 56 | case dataFileReadFailed(at: URL) 57 | case missingContentType(acceptableContentTypes: [String]) 58 | case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String) 59 | case unacceptableStatusCode(code: Int) 60 | } 61 | 62 | public enum ResponseSerializationFailureReason { 63 | case inputDataNil 64 | case inputDataNilOrZeroLength 65 | case inputFileNil 66 | case inputFileReadFailed(at: URL) 67 | case stringSerializationFailed(encoding: String.Encoding) 68 | case jsonSerializationFailed(error: Error) 69 | case propertyListSerializationFailed(error: Error) 70 | } 71 | 72 | case invalidURL(url: URLConvertible) 73 | case parameterEncodingFailed(reason: ParameterEncodingFailureReason) 74 | case multipartEncodingFailed(reason: MultipartEncodingFailureReason) 75 | case responseValidationFailed(reason: ResponseValidationFailureReason) 76 | case responseSerializationFailed(reason: ResponseSerializationFailureReason) 77 | } 78 | 79 | struct AdaptError: Error { 80 | let error: Error 81 | } 82 | 83 | extension Error { 84 | var underlyingAdaptError: Error? { return (self as? AdaptError)?.error } 85 | } 86 | 87 | extension HTTPError { 88 | 89 | public var isInvalidURLError: Bool { 90 | if case .invalidURL = self { return true } 91 | return false 92 | } 93 | 94 | public var isParameterEncodingError: Bool { 95 | if case .parameterEncodingFailed = self { return true } 96 | return false 97 | } 98 | 99 | public var isMultipartEncodingError: Bool { 100 | if case .multipartEncodingFailed = self { return true } 101 | return false 102 | } 103 | 104 | public var isResponseValidationError: Bool { 105 | if case .responseValidationFailed = self { return true } 106 | return false 107 | } 108 | 109 | public var isResponseSerializationError: Bool { 110 | if case .responseSerializationFailed = self { return true } 111 | return false 112 | } 113 | } 114 | 115 | extension HTTPError { 116 | 117 | public var urlConvertible: URLConvertible? { 118 | switch self { 119 | case .invalidURL(let url): 120 | return url 121 | default: 122 | return nil 123 | } 124 | } 125 | 126 | public var url: URL? { 127 | switch self { 128 | case .multipartEncodingFailed(let reason): 129 | return reason.url 130 | default: 131 | return nil 132 | } 133 | } 134 | 135 | public var underlyingError: Error? { 136 | switch self { 137 | case .parameterEncodingFailed(let reason): 138 | return reason.underlyingError 139 | case .multipartEncodingFailed(let reason): 140 | return reason.underlyingError 141 | case .responseSerializationFailed(let reason): 142 | return reason.underlyingError 143 | default: 144 | return nil 145 | } 146 | } 147 | 148 | public var acceptableContentTypes: [String]? { 149 | switch self { 150 | case .responseValidationFailed(let reason): 151 | return reason.acceptableContentTypes 152 | default: 153 | return nil 154 | } 155 | } 156 | 157 | public var responseContentType: String? { 158 | switch self { 159 | case .responseValidationFailed(let reason): 160 | return reason.responseContentType 161 | default: 162 | return nil 163 | } 164 | } 165 | 166 | public var responseCode: Int? { 167 | switch self { 168 | case .responseValidationFailed(let reason): 169 | return reason.responseCode 170 | default: 171 | return nil 172 | } 173 | } 174 | 175 | public var failedStringEncoding: String.Encoding? { 176 | switch self { 177 | case .responseSerializationFailed(let reason): 178 | return reason.failedStringEncoding 179 | default: 180 | return nil 181 | } 182 | } 183 | } 184 | 185 | extension HTTPError.ParameterEncodingFailureReason { 186 | var underlyingError: Error? { 187 | switch self { 188 | case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error): 189 | return error 190 | default: 191 | return nil 192 | } 193 | } 194 | } 195 | 196 | extension HTTPError.MultipartEncodingFailureReason { 197 | var url: URL? { 198 | switch self { 199 | case .bodyPartURLInvalid(let url), .bodyPartFilenameInvalid(let url), .bodyPartFileNotReachable(let url), 200 | .bodyPartFileIsDirectory(let url), .bodyPartFileSizeNotAvailable(let url), 201 | .bodyPartInputStreamCreationFailed(let url), .outputStreamCreationFailed(let url), 202 | .outputStreamFileAlreadyExists(let url), .outputStreamURLInvalid(let url), 203 | .bodyPartFileNotReachableWithError(let url, _), .bodyPartFileSizeQueryFailedWithError(let url, _): 204 | return url 205 | default: 206 | return nil 207 | } 208 | } 209 | 210 | var underlyingError: Error? { 211 | switch self { 212 | case .bodyPartFileNotReachableWithError(_, let error), .bodyPartFileSizeQueryFailedWithError(_, let error), 213 | .outputStreamWriteFailed(let error), .inputStreamReadFailed(let error): 214 | return error 215 | default: 216 | return nil 217 | } 218 | } 219 | } 220 | 221 | extension HTTPError.ResponseValidationFailureReason { 222 | var acceptableContentTypes: [String]? { 223 | switch self { 224 | case .missingContentType(let types), .unacceptableContentType(let types, _): 225 | return types 226 | default: 227 | return nil 228 | } 229 | } 230 | 231 | var responseContentType: String? { 232 | switch self { 233 | case .unacceptableContentType(_, let responseType): 234 | return responseType 235 | default: 236 | return nil 237 | } 238 | } 239 | 240 | var responseCode: Int? { 241 | switch self { 242 | case .unacceptableStatusCode(let code): 243 | return code 244 | default: 245 | return nil 246 | } 247 | } 248 | } 249 | 250 | extension HTTPError.ResponseSerializationFailureReason { 251 | var failedStringEncoding: String.Encoding? { 252 | switch self { 253 | case .stringSerializationFailed(let encoding): 254 | return encoding 255 | default: 256 | return nil 257 | } 258 | } 259 | 260 | var underlyingError: Error? { 261 | switch self { 262 | case .jsonSerializationFailed(let error), .propertyListSerializationFailed(let error): 263 | return error 264 | default: 265 | return nil 266 | } 267 | } 268 | } 269 | 270 | extension HTTPError: LocalizedError { 271 | public var errorDescription: String? { 272 | switch self { 273 | case .invalidURL(let url): 274 | return "Invalid url: \(url)" 275 | case .parameterEncodingFailed(let reason): 276 | return reason.localizedDescription 277 | case .multipartEncodingFailed(let reason): 278 | return reason.localizedDescription 279 | case .responseValidationFailed(let reason): 280 | return reason.localizedDescription 281 | case .responseSerializationFailed(let reason): 282 | return reason.localizedDescription 283 | } 284 | } 285 | } 286 | 287 | extension HTTPError.ParameterEncodingFailureReason { 288 | var localizedDescription: String { 289 | switch self { 290 | case .missingURL: 291 | return "URL request to encode was missing a URL" 292 | case .optionalDictionaryCastingFailed: 293 | return "Casting to [String: Any] type failed" 294 | case .jsonEncodingFailed(let error): 295 | return "JSON could not be encoded because of error:\n\(error.localizedDescription)" 296 | case .propertyListEncodingFailed(let error): 297 | return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)" 298 | } 299 | } 300 | } 301 | 302 | extension HTTPError.MultipartEncodingFailureReason { 303 | var localizedDescription: String { 304 | switch self { 305 | case .bodyPartURLInvalid(let url): 306 | return "The URL provided is not a file URL: \(url)" 307 | case .bodyPartFilenameInvalid(let url): 308 | return "The URL provided does not have a valid filename: \(url)" 309 | case .bodyPartFileNotReachable(let url): 310 | return "The URL provided is not reachable: \(url)" 311 | case .bodyPartFileNotReachableWithError(let url, let error): 312 | return ( 313 | "The system returned an error while checking the provided URL for " + 314 | "reachability.\nURL: \(url)\nError: \(error)" 315 | ) 316 | case .bodyPartFileIsDirectory(let url): 317 | return "The URL provided is a directory: \(url)" 318 | case .bodyPartFileSizeNotAvailable(let url): 319 | return "Could not fetch the file size from the provided URL: \(url)" 320 | case .bodyPartFileSizeQueryFailedWithError(let url, let error): 321 | return ( 322 | "The system returned an error while attempting to fetch the file size from the " + 323 | "provided URL.\nURL: \(url)\nError: \(error)" 324 | ) 325 | case .bodyPartInputStreamCreationFailed(let url): 326 | return "Failed to create an InputStream for the provided URL: \(url)" 327 | case .outputStreamCreationFailed(let url): 328 | return "Failed to create an OutputStream for URL: \(url)" 329 | case .outputStreamFileAlreadyExists(let url): 330 | return "A file already exists at the provided URL: \(url)" 331 | case .outputStreamURLInvalid(let url): 332 | return "The provided OutputStream URL is invalid: \(url)" 333 | case .outputStreamWriteFailed(let error): 334 | return "OutputStream write failed with error: \(error)" 335 | case .inputStreamReadFailed(let error): 336 | return "InputStream read failed with error: \(error)" 337 | } 338 | } 339 | } 340 | 341 | extension HTTPError.ResponseSerializationFailureReason { 342 | var localizedDescription: String { 343 | switch self { 344 | case .inputDataNil: 345 | return "Response could not be serialized, input data was nil." 346 | case .inputDataNilOrZeroLength: 347 | return "Response could not be serialized, input data was nil or zero length." 348 | case .inputFileNil: 349 | return "Response could not be serialized, input file was nil." 350 | case .inputFileReadFailed(let url): 351 | return "Response could not be serialized, input file could not be read: \(url)." 352 | case .stringSerializationFailed(let encoding): 353 | return "String could not be serialized with encoding: \(encoding)." 354 | case .jsonSerializationFailed(let error): 355 | return "JSON could not be serialized because of error:\n\(error.localizedDescription)" 356 | case .propertyListSerializationFailed(let error): 357 | return "PropertyList could not be serialized because of error:\n\(error.localizedDescription)" 358 | } 359 | } 360 | } 361 | 362 | extension HTTPError.ResponseValidationFailureReason { 363 | var localizedDescription: String { 364 | switch self { 365 | case .dataFileNil: 366 | return "Response could not be validated, data file was nil." 367 | case .dataFileReadFailed(let url): 368 | return "Response could not be validated, data file could not be read: \(url)." 369 | case .missingContentType(let types): 370 | return ( 371 | "Response Content-Type was missing and acceptable content types " + 372 | "(\(types.joined(separator: ","))) do not match \"*/*\"." 373 | ) 374 | case .unacceptableContentType(let acceptableTypes, let responseType): 375 | return ( 376 | "Response Content-Type \"\(responseType)\" does not match any acceptable types: " + 377 | "\(acceptableTypes.joined(separator: ","))." 378 | ) 379 | case .unacceptableStatusCode(let code): 380 | return "Response status code was unacceptable: \(code)." 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /Sources/ServiceManager/Encoding/FormDataEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormDataEncoding.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | #if os(iOS) || os(watchOS) || os(tvOS) 28 | import MobileCoreServices 29 | #elseif os(macOS) 30 | import CoreServices 31 | #endif 32 | 33 | public struct FormDataEncoding: ParameterEncoding { 34 | 35 | public static var `default`: FormDataEncoding { FormDataEncoding() } 36 | 37 | var data = MultipartFormData() 38 | 39 | public static func formData(_ data: MultipartFormData) -> FormDataEncoding { 40 | FormDataEncoding(data: data) 41 | } 42 | 43 | public init(data: MultipartFormData = MultipartFormData()) { 44 | self.data = data 45 | } 46 | 47 | public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { 48 | var urlRequest = try urlRequest.asURLRequest() 49 | 50 | let formData = data 51 | 52 | 53 | if urlRequest.headers["Content-Type"] == nil { 54 | urlRequest.headers.update(.contentType(formData.contentType)) 55 | } 56 | 57 | guard let parameters = parameters else { 58 | urlRequest.httpBody = try formData.encode() 59 | return urlRequest 60 | } 61 | 62 | for (key, value) in parameters { 63 | formData.append(Data("\(value)".utf8), withName: key) 64 | } 65 | 66 | urlRequest.httpBody = try formData.encode() 67 | return urlRequest 68 | } 69 | } 70 | 71 | open class MultipartFormData { 72 | 73 | struct EncodingCharacters { 74 | static let crlf = "\r\n" 75 | } 76 | 77 | struct BoundaryGenerator { 78 | enum BoundaryType { 79 | case initial, encapsulated, final 80 | } 81 | 82 | static func randomBoundary() -> String { 83 | return UUID().uuidString 84 | } 85 | 86 | static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data { 87 | let boundaryText: String 88 | 89 | switch boundaryType { 90 | case .initial: 91 | boundaryText = "--\(boundary)\(EncodingCharacters.crlf)" 92 | case .encapsulated: 93 | boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)" 94 | case .final: 95 | boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)" 96 | } 97 | 98 | return boundaryText.data(using: String.Encoding.utf8, allowLossyConversion: false)! 99 | } 100 | } 101 | 102 | class BodyPart { 103 | let headers: HTTPHeaders 104 | let bodyStream: InputStream 105 | let bodyContentLength: UInt64 106 | var hasInitialBoundary = false 107 | var hasFinalBoundary = false 108 | 109 | init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) { 110 | self.headers = headers 111 | self.bodyStream = bodyStream 112 | self.bodyContentLength = bodyContentLength 113 | } 114 | } 115 | 116 | open lazy var contentType: String = "multipart/form-data; boundary=\(self.boundary)" 117 | 118 | public var contentLength: UInt64 { return bodyParts.reduce(0) { $0 + $1.bodyContentLength } } 119 | 120 | public let boundary: String 121 | 122 | private var bodyParts: [BodyPart] 123 | private var bodyPartError: HTTPError? 124 | private let streamBufferSize: Int 125 | 126 | public init() { 127 | self.boundary = BoundaryGenerator.randomBoundary() 128 | self.bodyParts = [] 129 | 130 | self.streamBufferSize = 1024 131 | } 132 | 133 | public func append(_ data: Data, withName name: String) { 134 | let headers = contentHeaders(withName: name) 135 | let stream = InputStream(data: data) 136 | let length = UInt64(data.count) 137 | 138 | append(stream, withLength: length, headers: headers) 139 | } 140 | 141 | public func append(_ data: Data, withName name: String, mimeType: String) { 142 | let headers = contentHeaders(withName: name, mimeType: mimeType) 143 | let stream = InputStream(data: data) 144 | let length = UInt64(data.count) 145 | 146 | append(stream, withLength: length, headers: headers) 147 | } 148 | 149 | public func append(_ data: Data, withName name: String, fileName: String, mimeType: String) { 150 | let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) 151 | let stream = InputStream(data: data) 152 | let length = UInt64(data.count) 153 | 154 | append(stream, withLength: length, headers: headers) 155 | } 156 | 157 | public func append(_ fileURL: URL, withName name: String) { 158 | let fileName = fileURL.lastPathComponent 159 | let pathExtension = fileURL.pathExtension 160 | 161 | if !fileName.isEmpty && !pathExtension.isEmpty { 162 | let mime = mimeType(forPathExtension: pathExtension) 163 | append(fileURL, withName: name, fileName: fileName, mimeType: mime) 164 | } else { 165 | setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL)) 166 | } 167 | } 168 | 169 | public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) { 170 | let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) 171 | 172 | guard fileURL.isFileURL else { 173 | setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL)) 174 | return 175 | } 176 | 177 | do { 178 | let isReachable = try fileURL.checkPromisedItemIsReachable() 179 | guard isReachable else { 180 | setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL)) 181 | return 182 | } 183 | } catch { 184 | setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error)) 185 | return 186 | } 187 | 188 | var isDirectory: ObjCBool = false 189 | let path = fileURL.path 190 | 191 | guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else { 192 | setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL)) 193 | return 194 | } 195 | 196 | let bodyContentLength: UInt64 197 | 198 | do { 199 | guard let fileSize = try FileManager.default.attributesOfItem(atPath: path)[.size] as? NSNumber else { 200 | setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL)) 201 | return 202 | } 203 | 204 | bodyContentLength = fileSize.uint64Value 205 | } 206 | catch { 207 | setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error)) 208 | return 209 | } 210 | 211 | guard let stream = InputStream(url: fileURL) else { 212 | setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL)) 213 | return 214 | } 215 | 216 | append(stream, withLength: bodyContentLength, headers: headers) 217 | } 218 | 219 | public func append( 220 | _ stream: InputStream, 221 | withLength length: UInt64, 222 | name: String, 223 | fileName: String, 224 | mimeType: String) 225 | { 226 | let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) 227 | append(stream, withLength: length, headers: headers) 228 | } 229 | 230 | public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) { 231 | let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length) 232 | bodyParts.append(bodyPart) 233 | } 234 | 235 | public func encode() throws -> Data { 236 | if let bodyPartError = bodyPartError { 237 | throw bodyPartError 238 | } 239 | 240 | var encoded = Data() 241 | 242 | bodyParts.first?.hasInitialBoundary = true 243 | bodyParts.last?.hasFinalBoundary = true 244 | 245 | for bodyPart in bodyParts { 246 | let encodedData = try encode(bodyPart) 247 | encoded.append(encodedData) 248 | } 249 | 250 | return encoded 251 | } 252 | 253 | public func writeEncodedData(to fileURL: URL) throws { 254 | if let bodyPartError = bodyPartError { 255 | throw bodyPartError 256 | } 257 | 258 | if FileManager.default.fileExists(atPath: fileURL.path) { 259 | throw HTTPError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL)) 260 | } else if !fileURL.isFileURL { 261 | throw HTTPError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL)) 262 | } 263 | 264 | guard let outputStream = OutputStream(url: fileURL, append: false) else { 265 | throw HTTPError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL)) 266 | } 267 | 268 | outputStream.open() 269 | defer { outputStream.close() } 270 | 271 | self.bodyParts.first?.hasInitialBoundary = true 272 | self.bodyParts.last?.hasFinalBoundary = true 273 | 274 | for bodyPart in self.bodyParts { 275 | try write(bodyPart, to: outputStream) 276 | } 277 | } 278 | 279 | private func encode(_ bodyPart: BodyPart) throws -> Data { 280 | var encoded = Data() 281 | 282 | let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() 283 | encoded.append(initialData) 284 | 285 | let headerData = encodeHeaders(for: bodyPart) 286 | encoded.append(headerData) 287 | 288 | let bodyStreamData = try encodeBodyStream(for: bodyPart) 289 | encoded.append(bodyStreamData) 290 | 291 | if bodyPart.hasFinalBoundary { 292 | encoded.append(finalBoundaryData()) 293 | } 294 | 295 | return encoded 296 | } 297 | 298 | private func encodeHeaders(for bodyPart: BodyPart) -> Data { 299 | var headerText = "" 300 | 301 | for header in bodyPart.headers { 302 | headerText += "\(header.name): \(header.value)\(EncodingCharacters.crlf)" 303 | } 304 | headerText += EncodingCharacters.crlf 305 | 306 | return headerText.data(using: String.Encoding.utf8, allowLossyConversion: false)! 307 | } 308 | 309 | private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data { 310 | let inputStream = bodyPart.bodyStream 311 | inputStream.open() 312 | defer { inputStream.close() } 313 | 314 | var encoded = Data() 315 | 316 | while inputStream.hasBytesAvailable { 317 | var buffer = [UInt8](repeating: 0, count: streamBufferSize) 318 | let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) 319 | 320 | if let error = inputStream.streamError { 321 | throw HTTPError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error)) 322 | } 323 | 324 | if bytesRead > 0 { 325 | encoded.append(buffer, count: bytesRead) 326 | } else { 327 | break 328 | } 329 | } 330 | 331 | return encoded 332 | } 333 | 334 | private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws { 335 | try writeInitialBoundaryData(for: bodyPart, to: outputStream) 336 | try writeHeaderData(for: bodyPart, to: outputStream) 337 | try writeBodyStream(for: bodyPart, to: outputStream) 338 | try writeFinalBoundaryData(for: bodyPart, to: outputStream) 339 | } 340 | 341 | private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { 342 | let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() 343 | return try write(initialData, to: outputStream) 344 | } 345 | 346 | private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { 347 | let headerData = encodeHeaders(for: bodyPart) 348 | return try write(headerData, to: outputStream) 349 | } 350 | 351 | private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws { 352 | let inputStream = bodyPart.bodyStream 353 | 354 | inputStream.open() 355 | defer { inputStream.close() } 356 | 357 | while inputStream.hasBytesAvailable { 358 | var buffer = [UInt8](repeating: 0, count: streamBufferSize) 359 | let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) 360 | 361 | if let streamError = inputStream.streamError { 362 | throw HTTPError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError)) 363 | } 364 | 365 | if bytesRead > 0 { 366 | if buffer.count != bytesRead { 367 | buffer = Array(buffer[0.. 0, outputStream.hasSpaceAvailable { 394 | let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite) 395 | 396 | if let error = outputStream.streamError { 397 | throw HTTPError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error)) 398 | } 399 | 400 | bytesToWrite -= bytesWritten 401 | 402 | if bytesToWrite > 0 { 403 | buffer = Array(buffer[bytesWritten.. String { 409 | if 410 | let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), 411 | let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() 412 | { 413 | return contentType as String 414 | } 415 | 416 | return "application/octet-stream" 417 | } 418 | 419 | private func contentHeaders(withName name: String, fileName: String? = nil, mimeType: String? = nil) -> HTTPHeaders { 420 | var disposition = "form-data; name=\"\(name)\"" 421 | if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" } 422 | 423 | var headers = ["Content-Disposition": disposition] 424 | if let mimeType = mimeType { headers[contentType] = mimeType } 425 | 426 | return HTTPHeaders(headers) 427 | } 428 | 429 | private func initialBoundaryData() -> Data { 430 | return BoundaryGenerator.boundaryData(forBoundaryType: .initial, boundary: boundary) 431 | } 432 | 433 | private func encapsulatedBoundaryData() -> Data { 434 | return BoundaryGenerator.boundaryData(forBoundaryType: .encapsulated, boundary: boundary) 435 | } 436 | 437 | private func finalBoundaryData() -> Data { 438 | return BoundaryGenerator.boundaryData(forBoundaryType: .final, boundary: boundary) 439 | } 440 | 441 | private func setBodyPartError(withReason reason: HTTPError.MultipartEncodingFailureReason) { 442 | guard bodyPartError == nil else { return } 443 | bodyPartError = HTTPError.multipartEncodingFailed(reason: reason) 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /Sources/ServiceManager/HTTP/HTTPHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPHeader.swift 3 | // 4 | // Copyright © 2020 Bibin Jacob Pulickal (https://github.com/bibinjacobpulickal) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// An order-preserving and case-insensitive representation of HTTP headers. 28 | public struct HTTPHeaders { 29 | private var headers: [HTTPHeader] = [] 30 | 31 | /// Creates an empty instance. 32 | public init() {} 33 | 34 | /// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last 35 | /// name and value encountered. 36 | public init(_ headers: [HTTPHeader]) { 37 | self.init() 38 | 39 | headers.forEach { update($0) } 40 | } 41 | 42 | /// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name 43 | /// and value encountered. 44 | public init(_ dictionary: [String: String]) { 45 | self.init() 46 | 47 | dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) } 48 | } 49 | 50 | /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. 51 | /// 52 | /// - Parameters: 53 | /// - name: The `HTTPHeader` name. 54 | /// - value: The `HTTPHeader value. 55 | public mutating func add(name: String, value: String) { 56 | update(HTTPHeader(name: name, value: value)) 57 | } 58 | 59 | /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. 60 | /// 61 | /// - Parameter header: The `HTTPHeader` to update or append. 62 | public mutating func add(_ header: HTTPHeader) { 63 | update(header) 64 | } 65 | 66 | /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. 67 | /// 68 | /// - Parameters: 69 | /// - name: The `HTTPHeader` name. 70 | /// - value: The `HTTPHeader value. 71 | public mutating func update(name: String, value: String) { 72 | update(HTTPHeader(name: name, value: value)) 73 | } 74 | 75 | /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. 76 | /// 77 | /// - Parameter header: The `HTTPHeader` to update or append. 78 | public mutating func update(_ header: HTTPHeader) { 79 | guard let index = headers.index(of: header.name) else { 80 | headers.append(header) 81 | return 82 | } 83 | 84 | headers.replaceSubrange(index...index, with: [header]) 85 | } 86 | 87 | /// Case-insensitively removes an `HTTPHeader`, if it exists, from the instance. 88 | /// 89 | /// - Parameter name: The name of the `HTTPHeader` to remove. 90 | public mutating func remove(name: String) { 91 | guard let index = headers.index(of: name) else { return } 92 | 93 | headers.remove(at: index) 94 | } 95 | 96 | /// Sort the current instance by header name, case insensitively. 97 | public mutating func sort() { 98 | headers.sort { $0.name.lowercased() < $1.name.lowercased() } 99 | } 100 | 101 | /// Returns an instance sorted by header name. 102 | /// 103 | /// - Returns: A copy of the current instance sorted by name. 104 | public func sorted() -> HTTPHeaders { 105 | var headers = self 106 | headers.sort() 107 | 108 | return headers 109 | } 110 | 111 | /// Case-insensitively find a header's value by name. 112 | /// 113 | /// - Parameter name: The name of the header to search for, case-insensitively. 114 | /// 115 | /// - Returns: The value of header, if it exists. 116 | public func value(for name: String) -> String? { 117 | guard let index = headers.index(of: name) else { return nil } 118 | 119 | return headers[index].value 120 | } 121 | 122 | /// Case-insensitively access the header with the given name. 123 | /// 124 | /// - Parameter name: The name of the header. 125 | public subscript(_ name: String) -> String? { 126 | get { value(for: name) } 127 | set { 128 | if let value = newValue { 129 | update(name: name, value: value) 130 | } else { 131 | remove(name: name) 132 | } 133 | } 134 | } 135 | 136 | /// The dictionary representation of all headers. 137 | /// 138 | /// This representation does not preserve the current order of the instance. 139 | public var dictionary: [String: String] { 140 | let namesAndValues = headers.map { ($0.name, $0.value) } 141 | 142 | return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last }) 143 | } 144 | } 145 | 146 | extension HTTPHeaders: ExpressibleByDictionaryLiteral { 147 | public init(dictionaryLiteral elements: (String, String)...) { 148 | self.init() 149 | 150 | elements.forEach { update(name: $0.0, value: $0.1) } 151 | } 152 | } 153 | 154 | extension HTTPHeaders: ExpressibleByArrayLiteral { 155 | public init(arrayLiteral elements: HTTPHeader...) { 156 | self.init(elements) 157 | } 158 | } 159 | 160 | extension HTTPHeaders: Sequence { 161 | public func makeIterator() -> IndexingIterator<[HTTPHeader]> { 162 | headers.makeIterator() 163 | } 164 | } 165 | 166 | extension HTTPHeaders: Collection { 167 | public var startIndex: Int { 168 | headers.startIndex 169 | } 170 | 171 | public var endIndex: Int { 172 | headers.endIndex 173 | } 174 | 175 | public subscript(position: Int) -> HTTPHeader { 176 | headers[position] 177 | } 178 | 179 | public func index(after i: Int) -> Int { 180 | headers.index(after: i) 181 | } 182 | } 183 | 184 | extension HTTPHeaders: CustomStringConvertible { 185 | public var description: String { 186 | headers.map { $0.description } 187 | .joined(separator: "\n") 188 | } 189 | } 190 | 191 | // MARK: - HTTPHeader 192 | 193 | /// A representation of a single HTTP header's name / value pair. 194 | public struct HTTPHeader: Hashable { 195 | /// Name of the header. 196 | public let name: String 197 | 198 | /// Value of the header. 199 | public let value: String 200 | 201 | /// Creates an instance from the given `name` and `value`. 202 | /// 203 | /// - Parameters: 204 | /// - name: The name of the header. 205 | /// - value: The value of the header. 206 | public init(name: String, value: String) { 207 | self.name = name 208 | self.value = value 209 | } 210 | } 211 | 212 | extension HTTPHeader: CustomStringConvertible { 213 | public var description: String { 214 | "\(name): \(value)" 215 | } 216 | } 217 | 218 | extension HTTPHeader { 219 | /// Returns an `Accept` header. 220 | /// 221 | /// - Parameter value: The `Accept` value. 222 | /// - Returns: The header. 223 | public static func accept(_ value: String) -> HTTPHeader { 224 | HTTPHeader(name: "Accept", value: value) 225 | } 226 | 227 | /// Returns an `Accept-Charset` header. 228 | /// 229 | /// - Parameter value: The `Accept-Charset` value. 230 | /// - Returns: The header. 231 | public static func acceptCharset(_ value: String) -> HTTPHeader { 232 | HTTPHeader(name: "Accept-Charset", value: value) 233 | } 234 | 235 | /// Returns an `Accept-Language` header. 236 | /// 237 | /// BBServiceKit offers a default Accept-Language header that accumulates and encodes the system's preferred languages. 238 | /// Use `HTTPHeader.defaultAcceptLanguage`. 239 | /// 240 | /// - Parameter value: The `Accept-Language` value. 241 | /// 242 | /// - Returns: The header. 243 | public static func acceptLanguage(_ value: String) -> HTTPHeader { 244 | HTTPHeader(name: "Accept-Language", value: value) 245 | } 246 | 247 | /// Returns an `Accept-Encoding` header. 248 | /// 249 | /// BBServiceKit offers a default accept encoding value that provides the most common values. Use 250 | /// `HTTPHeader.defaultAcceptEncoding`. 251 | /// 252 | /// - Parameter value: The `Accept-Encoding` value. 253 | /// 254 | /// - Returns: The header 255 | public static func acceptEncoding(_ value: String) -> HTTPHeader { 256 | HTTPHeader(name: "Accept-Encoding", value: value) 257 | } 258 | 259 | /// Returns a `Basic` `Authorization` header using the `username` and `password` provided. 260 | /// 261 | /// - Parameters: 262 | /// - username: The username of the header. 263 | /// - password: The password of the header. 264 | /// 265 | /// - Returns: The header. 266 | public static func authorization(username: String, password: String) -> HTTPHeader { 267 | let credential = Data("\(username):\(password)".utf8).base64EncodedString() 268 | 269 | return authorization("Basic \(credential)") 270 | } 271 | 272 | /// Returns a `Bearer` `Authorization` header using the `bearerToken` provided 273 | /// 274 | /// - Parameter bearerToken: The bearer token. 275 | /// 276 | /// - Returns: The header. 277 | public static func authorization(bearerToken: String) -> HTTPHeader { 278 | authorization("Bearer \(bearerToken)") 279 | } 280 | 281 | /// Returns an `Authorization` header. 282 | /// 283 | /// BBServiceKit provides built-in methods to produce `Authorization` headers. For a Basic `Authorization` header use 284 | /// `HTTPHeader.authorization(username:password:)`. For a Bearer `Authorization` header, use 285 | /// `HTTPHeader.authorization(bearerToken:)`. 286 | /// 287 | /// - Parameter value: The `Authorization` value. 288 | /// 289 | /// - Returns: The header. 290 | public static func authorization(_ value: String) -> HTTPHeader { 291 | HTTPHeader(name: "Authorization", value: value) 292 | } 293 | 294 | /// Returns a `Content-Disposition` header. 295 | /// 296 | /// - Parameter value: The `Content-Disposition` value. 297 | /// 298 | /// - Returns: The header. 299 | public static func contentDisposition(_ value: String) -> HTTPHeader { 300 | HTTPHeader(name: "Content-Disposition", value: value) 301 | } 302 | 303 | /// Returns a `Content-Type` header. 304 | /// 305 | /// All BBServiceKit `ParameterEncoding`s and `ParameterEncoder`s set the `Content-Type` of the request, so it may not be necessary to manually 306 | /// set this value. 307 | /// 308 | /// - Parameter value: The `Content-Type` value. 309 | /// 310 | /// - Returns: The header. 311 | public static func contentType(_ value: String) -> HTTPHeader { 312 | HTTPHeader(name: "Content-Type", value: value) 313 | } 314 | 315 | /// Returns a `User-Agent` header. 316 | /// 317 | /// - Parameter value: The `User-Agent` value. 318 | /// 319 | /// - Returns: The header. 320 | public static func userAgent(_ value: String) -> HTTPHeader { 321 | HTTPHeader(name: "User-Agent", value: value) 322 | } 323 | } 324 | 325 | extension Array where Element == HTTPHeader { 326 | /// Case-insensitively finds the index of an `HTTPHeader` with the provided name, if it exists. 327 | func index(of name: String) -> Int? { 328 | let lowercasedName = name.lowercased() 329 | return firstIndex { $0.name.lowercased() == lowercasedName } 330 | } 331 | } 332 | 333 | // MARK: - Defaults 334 | 335 | public extension HTTPHeaders { 336 | /// The default set of `HTTPHeaders` used by BBServiceKit. Includes `Accept-Encoding`, `Accept-Language`, and 337 | /// `User-Agent`. 338 | static let `default`: HTTPHeaders = [.defaultAcceptEncoding, 339 | .defaultAcceptLanguage, 340 | .defaultUserAgent] 341 | } 342 | 343 | extension HTTPHeader { 344 | /// Returns BBServiceKit's default `Accept-Encoding` header, appropriate for the encodings supported by particular OS 345 | /// versions. 346 | /// 347 | /// See the [Accept-Encoding HTTP header documentation](https://tools.ietf.org/html/rfc7230#section-4.2.3) . 348 | public static let defaultAcceptEncoding: HTTPHeader = { 349 | let encodings: [String] 350 | if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) { 351 | encodings = ["br", "gzip", "deflate"] 352 | } else { 353 | encodings = ["gzip", "deflate"] 354 | } 355 | 356 | return .acceptEncoding(encodings.qualityEncoded()) 357 | }() 358 | 359 | /// Returns BBServiceKit's default `Accept-Language` header, generated by querying `Locale` for the user's 360 | /// `preferredLanguages`. 361 | /// 362 | /// See the [Accept-Language HTTP header documentation](https://tools.ietf.org/html/rfc7231#section-5.3.5). 363 | public static let defaultAcceptLanguage: HTTPHeader = { 364 | .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded()) 365 | }() 366 | 367 | /// Returns BBServiceKit's default `User-Agent` header. 368 | /// 369 | /// See the [User-Agent header documentation](https://tools.ietf.org/html/rfc7231#section-5.5.3). 370 | /// 371 | /// Example: `iOS Example/1.0 (org.BBServiceKit.iOS-Example; build:1; iOS 13.0.0) BBServiceKit/5.0.0` 372 | public static let defaultUserAgent: HTTPHeader = { 373 | let info = Bundle.main.infoDictionary 374 | let executable = (info?[kCFBundleExecutableKey as String] as? String) ?? 375 | (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ?? 376 | "Unknown" 377 | let bundle = info?[kCFBundleIdentifierKey as String] as? String ?? "Unknown" 378 | let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown" 379 | let appBuild = info?[kCFBundleVersionKey as String] as? String ?? "Unknown" 380 | 381 | let osNameVersion: String = { 382 | let version = ProcessInfo.processInfo.operatingSystemVersion 383 | let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" 384 | let osName: String = { 385 | #if os(iOS) 386 | #if targetEnvironment(macCatalyst) 387 | return "macOS(Catalyst)" 388 | #else 389 | return "iOS" 390 | #endif 391 | #elseif os(watchOS) 392 | return "watchOS" 393 | #elseif os(tvOS) 394 | return "tvOS" 395 | #elseif os(macOS) 396 | return "macOS" 397 | #elseif os(Linux) 398 | return "Linux" 399 | #elseif os(Windows) 400 | return "Windows" 401 | #else 402 | return "Unknown" 403 | #endif 404 | }() 405 | 406 | return "\(osName) \(versionString)" 407 | }() 408 | 409 | let BBServiceKitVersion = "BBServiceKit/\(version)" 410 | 411 | let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(BBServiceKitVersion)" 412 | 413 | return .userAgent(userAgent) 414 | }() 415 | } 416 | 417 | extension Collection where Element == String { 418 | func qualityEncoded() -> String { 419 | enumerated().map { index, encoding in 420 | let quality = 1.0 - (Double(index) * 0.1) 421 | return "\(encoding);q=\(quality)" 422 | }.joined(separator: ", ") 423 | } 424 | } 425 | 426 | // MARK: - System Type Extensions 427 | 428 | extension URLRequest { 429 | /// Returns `allHTTPHeaderFields` as `HTTPHeaders`. 430 | public var headers: HTTPHeaders { 431 | get { allHTTPHeaderFields.map(HTTPHeaders.init) ?? HTTPHeaders() } 432 | set { allHTTPHeaderFields = newValue.dictionary } 433 | } 434 | } 435 | 436 | extension HTTPURLResponse { 437 | /// Returns `allHeaderFields` as `HTTPHeaders`. 438 | public var headers: HTTPHeaders { 439 | (allHeaderFields as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() 440 | } 441 | } 442 | 443 | public extension URLSessionConfiguration { 444 | /// Returns `httpAdditionalHeaders` as `HTTPHeaders`. 445 | var headers: HTTPHeaders { 446 | get { (httpAdditionalHeaders as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() } 447 | set { httpAdditionalHeaders = newValue.dictionary } 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /ServiceManager.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = "1"; 4 | objectVersion = "46"; 5 | objects = { 6 | "OBJ_1" = { 7 | isa = "PBXProject"; 8 | attributes = { 9 | LastSwiftMigration = "9999"; 10 | LastUpgradeCheck = "9999"; 11 | }; 12 | buildConfigurationList = "OBJ_2"; 13 | compatibilityVersion = "Xcode 3.2"; 14 | developmentRegion = "en"; 15 | hasScannedForEncodings = "0"; 16 | knownRegions = ( 17 | "en" 18 | ); 19 | mainGroup = "OBJ_5"; 20 | productRefGroup = "OBJ_40"; 21 | projectDirPath = "."; 22 | targets = ( 23 | "ServiceManager::ServiceManager", 24 | "ServiceManager::SwiftPMPackageDescription", 25 | "ServiceManager::ServiceManagerPackageTests::ProductTarget", 26 | "ServiceManager::ServiceManagerTests" 27 | ); 28 | }; 29 | "OBJ_10" = { 30 | isa = "PBXFileReference"; 31 | path = "AnyDecoder.swift"; 32 | sourceTree = ""; 33 | }; 34 | "OBJ_11" = { 35 | isa = "PBXGroup"; 36 | children = ( 37 | "OBJ_12", 38 | "OBJ_13", 39 | "OBJ_14", 40 | "OBJ_15", 41 | "OBJ_16", 42 | "OBJ_17", 43 | "OBJ_18" 44 | ); 45 | name = "Encoding"; 46 | path = "Encoding"; 47 | sourceTree = ""; 48 | }; 49 | "OBJ_12" = { 50 | isa = "PBXFileReference"; 51 | path = "AnyEncoder.swift"; 52 | sourceTree = ""; 53 | }; 54 | "OBJ_13" = { 55 | isa = "PBXFileReference"; 56 | path = "EncodableExtensions.swift"; 57 | sourceTree = ""; 58 | }; 59 | "OBJ_14" = { 60 | isa = "PBXFileReference"; 61 | path = "FormDataEncoding.swift"; 62 | sourceTree = ""; 63 | }; 64 | "OBJ_15" = { 65 | isa = "PBXFileReference"; 66 | path = "HTTPEncoding.swift"; 67 | sourceTree = ""; 68 | }; 69 | "OBJ_16" = { 70 | isa = "PBXFileReference"; 71 | path = "JSONEncoding.swift"; 72 | sourceTree = ""; 73 | }; 74 | "OBJ_17" = { 75 | isa = "PBXFileReference"; 76 | path = "PropertyListEncoding.swift"; 77 | sourceTree = ""; 78 | }; 79 | "OBJ_18" = { 80 | isa = "PBXFileReference"; 81 | path = "URLEncoding.swift"; 82 | sourceTree = ""; 83 | }; 84 | "OBJ_19" = { 85 | isa = "PBXGroup"; 86 | children = ( 87 | "OBJ_20", 88 | "OBJ_21", 89 | "OBJ_22", 90 | "OBJ_23" 91 | ); 92 | name = "HTTP"; 93 | path = "HTTP"; 94 | sourceTree = ""; 95 | }; 96 | "OBJ_2" = { 97 | isa = "XCConfigurationList"; 98 | buildConfigurations = ( 99 | "OBJ_3", 100 | "OBJ_4" 101 | ); 102 | defaultConfigurationIsVisible = "0"; 103 | defaultConfigurationName = "Release"; 104 | }; 105 | "OBJ_20" = { 106 | isa = "PBXFileReference"; 107 | path = "HTTPError.swift"; 108 | sourceTree = ""; 109 | }; 110 | "OBJ_21" = { 111 | isa = "PBXFileReference"; 112 | path = "HTTPHeader.swift"; 113 | sourceTree = ""; 114 | }; 115 | "OBJ_22" = { 116 | isa = "PBXFileReference"; 117 | path = "HTTPMethod.swift"; 118 | sourceTree = ""; 119 | }; 120 | "OBJ_23" = { 121 | isa = "PBXFileReference"; 122 | path = "HTTPScheme.swift"; 123 | sourceTree = ""; 124 | }; 125 | "OBJ_24" = { 126 | isa = "PBXGroup"; 127 | children = ( 128 | "OBJ_25", 129 | "OBJ_26", 130 | "OBJ_27" 131 | ); 132 | name = "Protocols"; 133 | path = "Protocols"; 134 | sourceTree = ""; 135 | }; 136 | "OBJ_25" = { 137 | isa = "PBXFileReference"; 138 | path = "DataConvertible.swift"; 139 | sourceTree = ""; 140 | }; 141 | "OBJ_26" = { 142 | isa = "PBXFileReference"; 143 | path = "URLConvertible.swift"; 144 | sourceTree = ""; 145 | }; 146 | "OBJ_27" = { 147 | isa = "PBXFileReference"; 148 | path = "URLRequestConvertible.swift"; 149 | sourceTree = ""; 150 | }; 151 | "OBJ_28" = { 152 | isa = "PBXGroup"; 153 | children = ( 154 | "OBJ_29", 155 | "OBJ_30", 156 | "OBJ_31", 157 | "OBJ_32" 158 | ); 159 | name = "Route"; 160 | path = "Route"; 161 | sourceTree = ""; 162 | }; 163 | "OBJ_29" = { 164 | isa = "PBXFileReference"; 165 | path = "RequestComponent.swift"; 166 | sourceTree = ""; 167 | }; 168 | "OBJ_3" = { 169 | isa = "XCBuildConfiguration"; 170 | buildSettings = { 171 | CLANG_ENABLE_OBJC_ARC = "YES"; 172 | COMBINE_HIDPI_IMAGES = "YES"; 173 | COPY_PHASE_STRIP = "NO"; 174 | DEBUG_INFORMATION_FORMAT = "dwarf"; 175 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 176 | ENABLE_NS_ASSERTIONS = "YES"; 177 | GCC_OPTIMIZATION_LEVEL = "0"; 178 | GCC_PREPROCESSOR_DEFINITIONS = ( 179 | "$(inherited)", 180 | "SWIFT_PACKAGE=1", 181 | "DEBUG=1" 182 | ); 183 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 184 | ONLY_ACTIVE_ARCH = "YES"; 185 | OTHER_SWIFT_FLAGS = ( 186 | "$(inherited)", 187 | "-DXcode" 188 | ); 189 | PRODUCT_NAME = "$(TARGET_NAME)"; 190 | SDKROOT = "macosx"; 191 | SUPPORTED_PLATFORMS = ( 192 | "macosx", 193 | "iphoneos", 194 | "iphonesimulator", 195 | "appletvos", 196 | "appletvsimulator", 197 | "watchos", 198 | "watchsimulator" 199 | ); 200 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 201 | "$(inherited)", 202 | "SWIFT_PACKAGE", 203 | "DEBUG" 204 | ); 205 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 206 | USE_HEADERMAP = "NO"; 207 | }; 208 | name = "Debug"; 209 | }; 210 | "OBJ_30" = { 211 | isa = "PBXFileReference"; 212 | path = "ResponseComponent.swift"; 213 | sourceTree = ""; 214 | }; 215 | "OBJ_31" = { 216 | isa = "PBXFileReference"; 217 | path = "Route.swift"; 218 | sourceTree = ""; 219 | }; 220 | "OBJ_32" = { 221 | isa = "PBXFileReference"; 222 | path = "URLComponent.swift"; 223 | sourceTree = ""; 224 | }; 225 | "OBJ_33" = { 226 | isa = "PBXGroup"; 227 | children = ( 228 | "OBJ_34", 229 | "OBJ_35" 230 | ); 231 | name = "Service"; 232 | path = "Service"; 233 | sourceTree = ""; 234 | }; 235 | "OBJ_34" = { 236 | isa = "PBXFileReference"; 237 | path = "BBServiceKit.swift"; 238 | sourceTree = ""; 239 | }; 240 | "OBJ_35" = { 241 | isa = "PBXFileReference"; 242 | path = "Service.swift"; 243 | sourceTree = ""; 244 | }; 245 | "OBJ_36" = { 246 | isa = "PBXGroup"; 247 | children = ( 248 | "OBJ_37" 249 | ); 250 | name = "Tests"; 251 | path = ""; 252 | sourceTree = "SOURCE_ROOT"; 253 | }; 254 | "OBJ_37" = { 255 | isa = "PBXGroup"; 256 | children = ( 257 | "OBJ_38", 258 | "OBJ_39" 259 | ); 260 | name = "ServiceManagerTests"; 261 | path = "Tests/ServiceManagerTests"; 262 | sourceTree = "SOURCE_ROOT"; 263 | }; 264 | "OBJ_38" = { 265 | isa = "PBXFileReference"; 266 | path = "ServiceManagerTests.swift"; 267 | sourceTree = ""; 268 | }; 269 | "OBJ_39" = { 270 | isa = "PBXFileReference"; 271 | path = "XCTestManifests.swift"; 272 | sourceTree = ""; 273 | }; 274 | "OBJ_4" = { 275 | isa = "XCBuildConfiguration"; 276 | buildSettings = { 277 | CLANG_ENABLE_OBJC_ARC = "YES"; 278 | COMBINE_HIDPI_IMAGES = "YES"; 279 | COPY_PHASE_STRIP = "YES"; 280 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 281 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 282 | GCC_OPTIMIZATION_LEVEL = "s"; 283 | GCC_PREPROCESSOR_DEFINITIONS = ( 284 | "$(inherited)", 285 | "SWIFT_PACKAGE=1" 286 | ); 287 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 288 | OTHER_SWIFT_FLAGS = ( 289 | "$(inherited)", 290 | "-DXcode" 291 | ); 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SDKROOT = "macosx"; 294 | SUPPORTED_PLATFORMS = ( 295 | "macosx", 296 | "iphoneos", 297 | "iphonesimulator", 298 | "appletvos", 299 | "appletvsimulator", 300 | "watchos", 301 | "watchsimulator" 302 | ); 303 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 304 | "$(inherited)", 305 | "SWIFT_PACKAGE" 306 | ); 307 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 308 | USE_HEADERMAP = "NO"; 309 | }; 310 | name = "Release"; 311 | }; 312 | "OBJ_40" = { 313 | isa = "PBXGroup"; 314 | children = ( 315 | "ServiceManager::ServiceManagerTests::Product", 316 | "ServiceManager::ServiceManager::Product" 317 | ); 318 | name = "Products"; 319 | path = ""; 320 | sourceTree = "BUILT_PRODUCTS_DIR"; 321 | }; 322 | "OBJ_43" = { 323 | isa = "PBXFileReference"; 324 | path = "LICENSE"; 325 | sourceTree = ""; 326 | }; 327 | "OBJ_44" = { 328 | isa = "PBXFileReference"; 329 | path = "ServiceManager.podspec"; 330 | sourceTree = ""; 331 | }; 332 | "OBJ_45" = { 333 | isa = "PBXFileReference"; 334 | path = "README.md"; 335 | sourceTree = ""; 336 | }; 337 | "OBJ_47" = { 338 | isa = "XCConfigurationList"; 339 | buildConfigurations = ( 340 | "OBJ_48", 341 | "OBJ_49" 342 | ); 343 | defaultConfigurationIsVisible = "0"; 344 | defaultConfigurationName = "Release"; 345 | }; 346 | "OBJ_48" = { 347 | isa = "XCBuildConfiguration"; 348 | buildSettings = { 349 | ENABLE_TESTABILITY = "YES"; 350 | FRAMEWORK_SEARCH_PATHS = ( 351 | "$(inherited)", 352 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 353 | ); 354 | HEADER_SEARCH_PATHS = ( 355 | "$(inherited)" 356 | ); 357 | INFOPLIST_FILE = "ServiceManager.xcodeproj/ServiceManager_Info.plist"; 358 | IPHONEOS_DEPLOYMENT_TARGET = "9.0"; 359 | LD_RUNPATH_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 362 | ); 363 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 364 | OTHER_CFLAGS = ( 365 | "$(inherited)" 366 | ); 367 | OTHER_LDFLAGS = ( 368 | "$(inherited)" 369 | ); 370 | OTHER_SWIFT_FLAGS = ( 371 | "$(inherited)" 372 | ); 373 | PRODUCT_BUNDLE_IDENTIFIER = "ServiceManager"; 374 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 375 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 376 | SKIP_INSTALL = "YES"; 377 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 378 | "$(inherited)" 379 | ); 380 | SWIFT_VERSION = "5.0"; 381 | TARGET_NAME = "ServiceManager"; 382 | TVOS_DEPLOYMENT_TARGET = "9.0"; 383 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 384 | }; 385 | name = "Debug"; 386 | }; 387 | "OBJ_49" = { 388 | isa = "XCBuildConfiguration"; 389 | buildSettings = { 390 | ENABLE_TESTABILITY = "YES"; 391 | FRAMEWORK_SEARCH_PATHS = ( 392 | "$(inherited)", 393 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 394 | ); 395 | HEADER_SEARCH_PATHS = ( 396 | "$(inherited)" 397 | ); 398 | INFOPLIST_FILE = "ServiceManager.xcodeproj/ServiceManager_Info.plist"; 399 | IPHONEOS_DEPLOYMENT_TARGET = "9.0"; 400 | LD_RUNPATH_SEARCH_PATHS = ( 401 | "$(inherited)", 402 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 403 | ); 404 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 405 | OTHER_CFLAGS = ( 406 | "$(inherited)" 407 | ); 408 | OTHER_LDFLAGS = ( 409 | "$(inherited)" 410 | ); 411 | OTHER_SWIFT_FLAGS = ( 412 | "$(inherited)" 413 | ); 414 | PRODUCT_BUNDLE_IDENTIFIER = "ServiceManager"; 415 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 416 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 417 | SKIP_INSTALL = "YES"; 418 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 419 | "$(inherited)" 420 | ); 421 | SWIFT_VERSION = "5.0"; 422 | TARGET_NAME = "ServiceManager"; 423 | TVOS_DEPLOYMENT_TARGET = "9.0"; 424 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 425 | }; 426 | name = "Release"; 427 | }; 428 | "OBJ_5" = { 429 | isa = "PBXGroup"; 430 | children = ( 431 | "OBJ_6", 432 | "OBJ_7", 433 | "OBJ_36", 434 | "OBJ_40", 435 | "OBJ_43", 436 | "OBJ_44", 437 | "OBJ_45" 438 | ); 439 | path = ""; 440 | sourceTree = ""; 441 | }; 442 | "OBJ_50" = { 443 | isa = "PBXSourcesBuildPhase"; 444 | files = ( 445 | "OBJ_51", 446 | "OBJ_52", 447 | "OBJ_53", 448 | "OBJ_54", 449 | "OBJ_55", 450 | "OBJ_56", 451 | "OBJ_57", 452 | "OBJ_58", 453 | "OBJ_59", 454 | "OBJ_60", 455 | "OBJ_61", 456 | "OBJ_62", 457 | "OBJ_63", 458 | "OBJ_64", 459 | "OBJ_65", 460 | "OBJ_66", 461 | "OBJ_67", 462 | "OBJ_68", 463 | "OBJ_69", 464 | "OBJ_70", 465 | "OBJ_71" 466 | ); 467 | }; 468 | "OBJ_51" = { 469 | isa = "PBXBuildFile"; 470 | fileRef = "OBJ_10"; 471 | }; 472 | "OBJ_52" = { 473 | isa = "PBXBuildFile"; 474 | fileRef = "OBJ_12"; 475 | }; 476 | "OBJ_53" = { 477 | isa = "PBXBuildFile"; 478 | fileRef = "OBJ_13"; 479 | }; 480 | "OBJ_54" = { 481 | isa = "PBXBuildFile"; 482 | fileRef = "OBJ_14"; 483 | }; 484 | "OBJ_55" = { 485 | isa = "PBXBuildFile"; 486 | fileRef = "OBJ_15"; 487 | }; 488 | "OBJ_56" = { 489 | isa = "PBXBuildFile"; 490 | fileRef = "OBJ_16"; 491 | }; 492 | "OBJ_57" = { 493 | isa = "PBXBuildFile"; 494 | fileRef = "OBJ_17"; 495 | }; 496 | "OBJ_58" = { 497 | isa = "PBXBuildFile"; 498 | fileRef = "OBJ_18"; 499 | }; 500 | "OBJ_59" = { 501 | isa = "PBXBuildFile"; 502 | fileRef = "OBJ_20"; 503 | }; 504 | "OBJ_6" = { 505 | isa = "PBXFileReference"; 506 | explicitFileType = "sourcecode.swift"; 507 | path = "Package.swift"; 508 | sourceTree = ""; 509 | }; 510 | "OBJ_60" = { 511 | isa = "PBXBuildFile"; 512 | fileRef = "OBJ_21"; 513 | }; 514 | "OBJ_61" = { 515 | isa = "PBXBuildFile"; 516 | fileRef = "OBJ_22"; 517 | }; 518 | "OBJ_62" = { 519 | isa = "PBXBuildFile"; 520 | fileRef = "OBJ_23"; 521 | }; 522 | "OBJ_63" = { 523 | isa = "PBXBuildFile"; 524 | fileRef = "OBJ_25"; 525 | }; 526 | "OBJ_64" = { 527 | isa = "PBXBuildFile"; 528 | fileRef = "OBJ_26"; 529 | }; 530 | "OBJ_65" = { 531 | isa = "PBXBuildFile"; 532 | fileRef = "OBJ_27"; 533 | }; 534 | "OBJ_66" = { 535 | isa = "PBXBuildFile"; 536 | fileRef = "OBJ_29"; 537 | }; 538 | "OBJ_67" = { 539 | isa = "PBXBuildFile"; 540 | fileRef = "OBJ_30"; 541 | }; 542 | "OBJ_68" = { 543 | isa = "PBXBuildFile"; 544 | fileRef = "OBJ_31"; 545 | }; 546 | "OBJ_69" = { 547 | isa = "PBXBuildFile"; 548 | fileRef = "OBJ_32"; 549 | }; 550 | "OBJ_7" = { 551 | isa = "PBXGroup"; 552 | children = ( 553 | "OBJ_8" 554 | ); 555 | name = "Sources"; 556 | path = ""; 557 | sourceTree = "SOURCE_ROOT"; 558 | }; 559 | "OBJ_70" = { 560 | isa = "PBXBuildFile"; 561 | fileRef = "OBJ_34"; 562 | }; 563 | "OBJ_71" = { 564 | isa = "PBXBuildFile"; 565 | fileRef = "OBJ_35"; 566 | }; 567 | "OBJ_72" = { 568 | isa = "PBXFrameworksBuildPhase"; 569 | files = ( 570 | ); 571 | }; 572 | "OBJ_74" = { 573 | isa = "XCConfigurationList"; 574 | buildConfigurations = ( 575 | "OBJ_75", 576 | "OBJ_76" 577 | ); 578 | defaultConfigurationIsVisible = "0"; 579 | defaultConfigurationName = "Release"; 580 | }; 581 | "OBJ_75" = { 582 | isa = "XCBuildConfiguration"; 583 | buildSettings = { 584 | LD = "/usr/bin/true"; 585 | OTHER_SWIFT_FLAGS = ( 586 | "-swift-version", 587 | "5", 588 | "-I", 589 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 590 | "-sdk", 591 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 592 | "-package-description-version", 593 | "5.0.0" 594 | ); 595 | SWIFT_VERSION = "5.0"; 596 | }; 597 | name = "Debug"; 598 | }; 599 | "OBJ_76" = { 600 | isa = "XCBuildConfiguration"; 601 | buildSettings = { 602 | LD = "/usr/bin/true"; 603 | OTHER_SWIFT_FLAGS = ( 604 | "-swift-version", 605 | "5", 606 | "-I", 607 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 608 | "-sdk", 609 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 610 | "-package-description-version", 611 | "5.0.0" 612 | ); 613 | SWIFT_VERSION = "5.0"; 614 | }; 615 | name = "Release"; 616 | }; 617 | "OBJ_77" = { 618 | isa = "PBXSourcesBuildPhase"; 619 | files = ( 620 | "OBJ_78" 621 | ); 622 | }; 623 | "OBJ_78" = { 624 | isa = "PBXBuildFile"; 625 | fileRef = "OBJ_6"; 626 | }; 627 | "OBJ_8" = { 628 | isa = "PBXGroup"; 629 | children = ( 630 | "OBJ_9", 631 | "OBJ_11", 632 | "OBJ_19", 633 | "OBJ_24", 634 | "OBJ_28", 635 | "OBJ_33" 636 | ); 637 | name = "ServiceManager"; 638 | path = "Sources/ServiceManager"; 639 | sourceTree = "SOURCE_ROOT"; 640 | }; 641 | "OBJ_80" = { 642 | isa = "XCConfigurationList"; 643 | buildConfigurations = ( 644 | "OBJ_81", 645 | "OBJ_82" 646 | ); 647 | defaultConfigurationIsVisible = "0"; 648 | defaultConfigurationName = "Release"; 649 | }; 650 | "OBJ_81" = { 651 | isa = "XCBuildConfiguration"; 652 | buildSettings = { 653 | }; 654 | name = "Debug"; 655 | }; 656 | "OBJ_82" = { 657 | isa = "XCBuildConfiguration"; 658 | buildSettings = { 659 | }; 660 | name = "Release"; 661 | }; 662 | "OBJ_83" = { 663 | isa = "PBXTargetDependency"; 664 | target = "ServiceManager::ServiceManagerTests"; 665 | }; 666 | "OBJ_85" = { 667 | isa = "XCConfigurationList"; 668 | buildConfigurations = ( 669 | "OBJ_86", 670 | "OBJ_87" 671 | ); 672 | defaultConfigurationIsVisible = "0"; 673 | defaultConfigurationName = "Release"; 674 | }; 675 | "OBJ_86" = { 676 | isa = "XCBuildConfiguration"; 677 | buildSettings = { 678 | CLANG_ENABLE_MODULES = "YES"; 679 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 680 | FRAMEWORK_SEARCH_PATHS = ( 681 | "$(inherited)", 682 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 683 | ); 684 | HEADER_SEARCH_PATHS = ( 685 | "$(inherited)" 686 | ); 687 | INFOPLIST_FILE = "ServiceManager.xcodeproj/ServiceManagerTests_Info.plist"; 688 | IPHONEOS_DEPLOYMENT_TARGET = "14.0"; 689 | LD_RUNPATH_SEARCH_PATHS = ( 690 | "$(inherited)", 691 | "@loader_path/../Frameworks", 692 | "@loader_path/Frameworks" 693 | ); 694 | MACOSX_DEPLOYMENT_TARGET = "10.15"; 695 | OTHER_CFLAGS = ( 696 | "$(inherited)" 697 | ); 698 | OTHER_LDFLAGS = ( 699 | "$(inherited)" 700 | ); 701 | OTHER_SWIFT_FLAGS = ( 702 | "$(inherited)" 703 | ); 704 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 705 | "$(inherited)" 706 | ); 707 | SWIFT_VERSION = "5.0"; 708 | TARGET_NAME = "ServiceManagerTests"; 709 | TVOS_DEPLOYMENT_TARGET = "9.0"; 710 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 711 | }; 712 | name = "Debug"; 713 | }; 714 | "OBJ_87" = { 715 | isa = "XCBuildConfiguration"; 716 | buildSettings = { 717 | CLANG_ENABLE_MODULES = "YES"; 718 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 719 | FRAMEWORK_SEARCH_PATHS = ( 720 | "$(inherited)", 721 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 722 | ); 723 | HEADER_SEARCH_PATHS = ( 724 | "$(inherited)" 725 | ); 726 | INFOPLIST_FILE = "ServiceManager.xcodeproj/ServiceManagerTests_Info.plist"; 727 | IPHONEOS_DEPLOYMENT_TARGET = "14.0"; 728 | LD_RUNPATH_SEARCH_PATHS = ( 729 | "$(inherited)", 730 | "@loader_path/../Frameworks", 731 | "@loader_path/Frameworks" 732 | ); 733 | MACOSX_DEPLOYMENT_TARGET = "10.15"; 734 | OTHER_CFLAGS = ( 735 | "$(inherited)" 736 | ); 737 | OTHER_LDFLAGS = ( 738 | "$(inherited)" 739 | ); 740 | OTHER_SWIFT_FLAGS = ( 741 | "$(inherited)" 742 | ); 743 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 744 | "$(inherited)" 745 | ); 746 | SWIFT_VERSION = "5.0"; 747 | TARGET_NAME = "ServiceManagerTests"; 748 | TVOS_DEPLOYMENT_TARGET = "9.0"; 749 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 750 | }; 751 | name = "Release"; 752 | }; 753 | "OBJ_88" = { 754 | isa = "PBXSourcesBuildPhase"; 755 | files = ( 756 | "OBJ_89", 757 | "OBJ_90" 758 | ); 759 | }; 760 | "OBJ_89" = { 761 | isa = "PBXBuildFile"; 762 | fileRef = "OBJ_38"; 763 | }; 764 | "OBJ_9" = { 765 | isa = "PBXGroup"; 766 | children = ( 767 | "OBJ_10" 768 | ); 769 | name = "Decoding"; 770 | path = "Decoding"; 771 | sourceTree = ""; 772 | }; 773 | "OBJ_90" = { 774 | isa = "PBXBuildFile"; 775 | fileRef = "OBJ_39"; 776 | }; 777 | "OBJ_91" = { 778 | isa = "PBXFrameworksBuildPhase"; 779 | files = ( 780 | "OBJ_92" 781 | ); 782 | }; 783 | "OBJ_92" = { 784 | isa = "PBXBuildFile"; 785 | fileRef = "ServiceManager::ServiceManager::Product"; 786 | }; 787 | "OBJ_93" = { 788 | isa = "PBXTargetDependency"; 789 | target = "ServiceManager::ServiceManager"; 790 | }; 791 | "ServiceManager::ServiceManager" = { 792 | isa = "PBXNativeTarget"; 793 | buildConfigurationList = "OBJ_47"; 794 | buildPhases = ( 795 | "OBJ_50", 796 | "OBJ_72" 797 | ); 798 | dependencies = ( 799 | ); 800 | name = "ServiceManager"; 801 | productName = "ServiceManager"; 802 | productReference = "ServiceManager::ServiceManager::Product"; 803 | productType = "com.apple.product-type.framework"; 804 | }; 805 | "ServiceManager::ServiceManager::Product" = { 806 | isa = "PBXFileReference"; 807 | path = "ServiceManager.framework"; 808 | sourceTree = "BUILT_PRODUCTS_DIR"; 809 | }; 810 | "ServiceManager::ServiceManagerPackageTests::ProductTarget" = { 811 | isa = "PBXAggregateTarget"; 812 | buildConfigurationList = "OBJ_80"; 813 | buildPhases = ( 814 | ); 815 | dependencies = ( 816 | "OBJ_83" 817 | ); 818 | name = "ServiceManagerPackageTests"; 819 | productName = "ServiceManagerPackageTests"; 820 | }; 821 | "ServiceManager::ServiceManagerTests" = { 822 | isa = "PBXNativeTarget"; 823 | buildConfigurationList = "OBJ_85"; 824 | buildPhases = ( 825 | "OBJ_88", 826 | "OBJ_91" 827 | ); 828 | dependencies = ( 829 | "OBJ_93" 830 | ); 831 | name = "ServiceManagerTests"; 832 | productName = "ServiceManagerTests"; 833 | productReference = "ServiceManager::ServiceManagerTests::Product"; 834 | productType = "com.apple.product-type.bundle.unit-test"; 835 | }; 836 | "ServiceManager::ServiceManagerTests::Product" = { 837 | isa = "PBXFileReference"; 838 | path = "ServiceManagerTests.xctest"; 839 | sourceTree = "BUILT_PRODUCTS_DIR"; 840 | }; 841 | "ServiceManager::SwiftPMPackageDescription" = { 842 | isa = "PBXNativeTarget"; 843 | buildConfigurationList = "OBJ_74"; 844 | buildPhases = ( 845 | "OBJ_77" 846 | ); 847 | dependencies = ( 848 | ); 849 | name = "ServiceManagerPackageDescription"; 850 | productName = "ServiceManagerPackageDescription"; 851 | productType = "com.apple.product-type.framework"; 852 | }; 853 | }; 854 | rootObject = "OBJ_1"; 855 | } 856 | --------------------------------------------------------------------------------