├── .github └── workflows │ └── main.yml ├── .gitignore ├── .jazzy.yaml ├── .swiftlint.yml ├── CHANGELOG.md ├── Examples ├── Communication │ ├── DemoEnvironment.swift │ ├── Interceptors │ │ └── ServiceErrorInterceptor.swift │ ├── Middleware │ │ └── CryptoMiddleware.swift │ ├── Models │ │ ├── Domain │ │ │ ├── City.swift │ │ │ ├── EncryptedModel.swift │ │ │ └── FileResponse.swift │ │ └── Rest │ │ │ ├── RSCity.swift │ │ │ ├── RSEncryptedModel.swift │ │ │ └── RSFileResponse.swift │ ├── Repositories │ │ ├── CitiesRepository.swift │ │ └── MiscRepository.swift │ └── Transformers │ │ ├── CitiesTransformer.swift │ │ ├── CityTransformer.swift │ │ ├── EncryptedModelTransformer.swift │ │ └── FileUploadTransformer.swift ├── Screens │ ├── AppDelegate.swift │ ├── City Explorer │ │ ├── CityExplorerDetails.swift │ │ └── CityExplorerView.swift │ ├── ContentView.swift │ ├── DemoApp.swift │ ├── Encrypted Communication │ │ └── EncryptedCommunicationView.swift │ ├── File Downloader │ │ └── FileDownloader.swift │ ├── File Uploader │ │ ├── FileUploader.swift │ │ └── FileUploaderUtils.swift │ ├── Pinning │ │ └── CertificatePinningView.swift │ ├── Reachability │ │ └── Reachability.swift │ ├── TermiNetworkExamplesApp.swift │ └── UIHelpers.swift └── Supporting Files │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-small-50.png │ │ ├── icon-small-50@2x.png │ │ ├── icon-small.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── icon.png │ │ ├── icon@2x.png │ │ ├── ios-marketing.png │ │ ├── notification-icon@2x.png │ │ ├── notification-icon@3x.png │ │ ├── notification-icon~ipad.png │ │ └── notification-icon~ipad@2x.png │ ├── Contents.json │ └── TerrmiNetworkSplash.imageset │ │ ├── Contents.json │ │ └── TermiNetworkLogo-2.png │ ├── Info.plist │ ├── Launch Screen.storyboard │ └── MockData.bundle │ └── Cities │ ├── Details │ ├── 23.json │ ├── 24.json │ ├── 25.json │ ├── 26.json │ ├── 27.json │ └── 28.json │ ├── Images │ ├── 23.jpg │ ├── 24.jpg │ ├── 25.jpg │ ├── 26.jpg │ ├── 27.jpg │ └── 28.jpg │ ├── Thumbs │ ├── 23.jpg │ ├── 24.jpg │ ├── 25.jpg │ ├── 26.jpg │ ├── 27.jpg │ └── 28.jpg │ └── cities.json ├── LICENSE ├── Package.swift ├── README.md ├── Scripts ├── generate_changelog.sh └── generate_docs.sh ├── Source ├── Cache.swift ├── CertificatePinningManager.swift ├── Client.swift ├── Configuration.swift ├── EndpointConfiguration.swift ├── Enums │ ├── EnvironmentType.swift │ ├── HttpMethod.swift │ ├── InterceptionAction.swift │ ├── MultipartFormDataStream.swift │ ├── Path.swift │ ├── PathType.swift │ ├── QueueFailureMode.swift │ ├── RequestBodyType.swift │ ├── RequestType.swift │ ├── TNError.swift │ └── URLScheme.swift ├── Environment.swift ├── Extensions │ ├── Data+Extensions.swift │ ├── Dictionary+Extensions.swift │ ├── Operations │ │ ├── Decodable+Transformer.swift │ │ ├── Request+DataOperations.swift │ │ ├── Request+FileOperations.swift │ │ └── Request+FileOperationsAsync.swift │ ├── Request+Async.swift │ ├── Request+Interceptors.swift │ ├── Request+Middleware.swift │ ├── Request+Mock.swift │ ├── Request+NSCopying.swift │ ├── Request+ResponseHeaders.swift │ ├── Request+ResponseTypes.swift │ ├── UIImage+Extensions.swift │ ├── UIImageView+Extensions.swift │ └── URLRequest+Extensions.swift ├── FileStreamer.swift ├── Helpers │ ├── MultipartFormDataHelpers.swift │ ├── RequestBodyGenerators.swift │ └── RequestHelpers.swift ├── Log.swift ├── MultipartFormDataPartType.swift ├── Operation.swift ├── Protocols │ ├── EndpointProtocol.swift │ ├── InterceptorProtocol.swift │ └── RequestMiddlewareProtocol.swift ├── Queue.swift ├── Reachability.swift ├── Request.swift ├── Session.swift ├── SessionTaskFactory.swift ├── SwiftUI │ └── Image.swift └── Types.swift ├── TermiNetwork.podspec ├── TermiNetwork.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ ├── WorkspaceSettings.xcsettings │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ ├── TermiNetwork.xcscheme │ ├── TermiNetworkExamples (iOS).xcscheme │ └── TermiNetworkTests.xcscheme ├── TermiNetwork ├── Info.plist ├── TermiNetwork.entitlements └── TermiNetwork.h ├── TermiNetworkDiagram.svg ├── TermiNetworkLogo.svg ├── Tests ├── CryptoMiddleware.swift ├── DoNothingInterceptor.swift ├── Env.swift ├── GlobalInterceptor.swift ├── Info.plist ├── MockData.bundle │ └── main-repo │ │ └── headers.json ├── Models │ ├── Domain │ │ └── TestModel.swift │ └── Rest │ │ ├── EncryptedModel.swift │ │ ├── ErrorModel.swift │ │ ├── FileResponse.swift │ │ ├── StatusCode.swift │ │ ├── TestHeaders.swift │ │ ├── TestJSONParams.swift │ │ └── TestParams.swift ├── StatusCodeTransformer.swift ├── TestCache.swift ├── TestConfiguration.swift ├── TestDownloadOperations.swift ├── TestDownloadOperationsAsync.swift ├── TestEnvironment.swift ├── TestExtensions.swift ├── TestHelpers.swift ├── TestInterceptors.swift ├── TestMockRequestDeprecated.swift ├── TestMockedRequest.swift ├── TestPinning.swift ├── TestQueue.swift ├── TestReachability.swift ├── TestRepository.swift ├── TestRequest.swift ├── TestRequestAsync.swift ├── TestTNErrors.swift ├── TestTransformer.swift ├── TestTransformers.swift ├── TestUploadOperations.swift ├── TestUploadOperationsAsync.swift ├── TestUploadTransformer.swift ├── UnauthorizedInterceptor.swift ├── forums.swift.org.cer ├── photo.jpg ├── terminetwork.billp.dev.cer └── www.billp.dev.cer └── docs ├── Classes.html ├── Classes ├── Cache.html ├── Client.html ├── Configuration.html ├── EndpointConfiguration.html ├── Environment.html ├── ImageLoader.html ├── Operation.html ├── Queue.html ├── Reachability.html ├── Request.html ├── RouteConfiguration.html ├── Router.html ├── TNCache.html ├── TNConfiguration.html ├── TNEnvironment.html ├── TNLog.html ├── TNMultipartFormDataHelpers.html ├── TNMultipartFormDataHelpers │ └── Constants.html ├── TNMultipartFormDataStream.html ├── TNMultipartFormDataStream │ ├── Constants.html │ └── Streams.html ├── TNOperation.html ├── TNQueue.html ├── TNRequest.html ├── TNRequestBodyGenerators.html ├── TNRequestHelpers.html ├── TNRouteConfiguration.html ├── TNRouter.html ├── TNSession.html ├── TNSessionTaskFactory.html ├── TNTransformer.html └── Transformer.html ├── Enums.html ├── Enums ├── InteceptionActionType.html ├── InterceptionAction.html ├── Method.html ├── MultipartFormDataPartType.html ├── Path.html ├── QueueFailureMode.html ├── ReachabilityState.html ├── RequestBodyType.html ├── SNPathType.html ├── TNError.html ├── TNMethod.html ├── TNMultipartBodyPart.html ├── TNMultipartFormDataPartType.html ├── TNPath.html ├── TNQueueFailureMode.html ├── TNRequestBodyType.html ├── TNURLScheme.html └── URLScheme.html ├── Extensions.html ├── Extensions ├── Data.html ├── Decodable.html ├── Dictionary.html ├── String.html ├── UIImageView.html └── URLRequest.html ├── Protocols.html ├── Protocols ├── EndpointProtocol.html ├── EnvironmentProtocol.html ├── InterceptorProtocol.html ├── RequestMiddlewareProtocol.html ├── RouteProtocol.html ├── TNEnvironmentProtocol.html ├── TNErrorHandlerProtocol.html ├── TNRequestMiddlewareProtocol.html ├── TNRouteProtocol.html ├── TNRouterProtocol.html └── TransformerProtocol.html ├── Structs.html ├── Structs ├── Image.html └── TNImage.html ├── Typealiases.html ├── badge.svg ├── css ├── highlight.css └── jazzy.css ├── docsets ├── .docset │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── Documents │ │ ├── Classes.html │ │ ├── Classes │ │ │ ├── TNConfiguration.html │ │ │ ├── TNEnvironment.html │ │ │ ├── TNOperation.html │ │ │ ├── TNQueue.html │ │ │ ├── TNRequest.html │ │ │ ├── TNRouteConfiguration.html │ │ │ └── TNRouter.html │ │ ├── Enums.html │ │ ├── Enums │ │ │ ├── TNError.html │ │ │ ├── TNMethod.html │ │ │ ├── TNPath.html │ │ │ ├── TNQueueFailureMode.html │ │ │ ├── TNRequestBodyType.html │ │ │ └── TNURLScheme.html │ │ ├── Extensions.html │ │ ├── Extensions │ │ │ ├── Data.html │ │ │ └── UIImageView.html │ │ ├── Protocols.html │ │ ├── Protocols │ │ │ ├── TNEnvironmentProtocol.html │ │ │ ├── TNRequestMiddlewareProtocol.html │ │ │ └── TNRouterProtocol.html │ │ ├── Typealiases.html │ │ ├── badge.svg │ │ ├── css │ │ │ ├── highlight.css │ │ │ └── jazzy.css │ │ ├── img │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ └── gh.png │ │ ├── index.html │ │ ├── js │ │ │ ├── jazzy.js │ │ │ └── jquery.min.js │ │ ├── search.json │ │ └── undocumented.json │ │ └── docSet.dsidx ├── .tgz ├── TermiNetwork.docset │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── Documents │ │ ├── Classes.html │ │ ├── Classes │ │ │ ├── Cache.html │ │ │ ├── Client.html │ │ │ ├── Configuration.html │ │ │ ├── EndpointConfiguration.html │ │ │ ├── Environment.html │ │ │ ├── ImageLoader.html │ │ │ ├── Operation.html │ │ │ ├── Queue.html │ │ │ ├── Reachability.html │ │ │ ├── Request.html │ │ │ ├── RouteConfiguration.html │ │ │ ├── Router.html │ │ │ ├── TNCache.html │ │ │ ├── TNConfiguration.html │ │ │ ├── TNEnvironment.html │ │ │ ├── TNLog.html │ │ │ ├── TNMultipartFormDataHelpers.html │ │ │ ├── TNMultipartFormDataHelpers │ │ │ │ └── Constants.html │ │ │ ├── TNMultipartFormDataStream.html │ │ │ ├── TNMultipartFormDataStream │ │ │ │ ├── Constants.html │ │ │ │ └── Streams.html │ │ │ ├── TNOperation.html │ │ │ ├── TNQueue.html │ │ │ ├── TNRequest.html │ │ │ ├── TNRequestBodyGenerators.html │ │ │ ├── TNRequestHelpers.html │ │ │ ├── TNRouteConfiguration.html │ │ │ ├── TNRouter.html │ │ │ ├── TNSession.html │ │ │ ├── TNSessionTaskFactory.html │ │ │ ├── TNTransformer.html │ │ │ └── Transformer.html │ │ ├── Enums.html │ │ ├── Enums │ │ │ ├── InteceptionActionType.html │ │ │ ├── InterceptionAction.html │ │ │ ├── Method.html │ │ │ ├── MultipartFormDataPartType.html │ │ │ ├── Path.html │ │ │ ├── QueueFailureMode.html │ │ │ ├── ReachabilityState.html │ │ │ ├── RequestBodyType.html │ │ │ ├── SNPathType.html │ │ │ ├── TNError.html │ │ │ ├── TNMethod.html │ │ │ ├── TNMultipartBodyPart.html │ │ │ ├── TNMultipartFormDataPartType.html │ │ │ ├── TNPath.html │ │ │ ├── TNQueueFailureMode.html │ │ │ ├── TNRequestBodyType.html │ │ │ ├── TNURLScheme.html │ │ │ └── URLScheme.html │ │ ├── Extensions.html │ │ ├── Extensions │ │ │ ├── Data.html │ │ │ ├── Decodable.html │ │ │ ├── Dictionary.html │ │ │ ├── String.html │ │ │ ├── UIImageView.html │ │ │ └── URLRequest.html │ │ ├── Protocols.html │ │ ├── Protocols │ │ │ ├── EndpointProtocol.html │ │ │ ├── EnvironmentProtocol.html │ │ │ ├── InterceptorProtocol.html │ │ │ ├── RequestMiddlewareProtocol.html │ │ │ ├── RouteProtocol.html │ │ │ ├── TNEnvironmentProtocol.html │ │ │ ├── TNErrorHandlerProtocol.html │ │ │ ├── TNRequestMiddlewareProtocol.html │ │ │ ├── TNRouteProtocol.html │ │ │ ├── TNRouterProtocol.html │ │ │ └── TransformerProtocol.html │ │ ├── Structs.html │ │ ├── Structs │ │ │ ├── Image.html │ │ │ └── TNImage.html │ │ ├── Typealiases.html │ │ ├── badge.svg │ │ ├── css │ │ │ ├── highlight.css │ │ │ └── jazzy.css │ │ ├── img │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ ├── gh.png │ │ │ └── spinner.gif │ │ ├── index.html │ │ ├── js │ │ │ ├── jazzy.js │ │ │ ├── jazzy.search.js │ │ │ ├── jquery.min.js │ │ │ ├── lunr.min.js │ │ │ └── typeahead.jquery.js │ │ ├── search.json │ │ └── undocumented.json │ │ └── docSet.dsidx ├── TermiNetwork.tgz └── TermiNetwork.xml ├── img ├── carat.png ├── dash.png ├── gh.png └── spinner.gif ├── index.html ├── js ├── jazzy.js ├── jazzy.search.js ├── jquery.min.js ├── lunr.min.js └── typeahead.jquery.js ├── search.json └── undocumented.json /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | jobs: 5 | Run_workspace_Tests: 6 | runs-on: macOS-latest 7 | steps: 8 | - uses: actions/checkout@v1 9 | - name: List available Xcode versions 10 | run: ls /Applications | grep Xcode 11 | - name: Select Xcode 12 | run: sudo xcode-select -switch /Applications/Xcode_16.1.app && /usr/bin/xcodebuild -version 13 | - name: Run unit tests 14 | run: xcodebuild test -scheme TermiNetworkTests -destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2' 15 | - name: Upload coverage to Codecov 16 | uses: codecov/codecov-action@v1.2.1 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | .build/ 4 | 5 | # Xcode 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata/ 16 | *.xccheckout 17 | profile 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | 23 | # Bundler 24 | .bundle 25 | 26 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 27 | # Carthage/Checkouts 28 | 29 | Carthage/Build 30 | 31 | # We recommend against adding the Pods directory to your .gitignore. However 32 | # you should judge for yourself, the pros and cons are mentioned at: 33 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 34 | # 35 | # Note: if you ignore the Pods directory, make sure to uncomment 36 | # `pod install` in .travis.yml 37 | # 38 | # Pods/ 39 | -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | author: Vasilis Panagiotopoulos 2 | author_url: https://github.com/billp/TermiNetwork 3 | github_url: https://github.com/billp/TermiNetwork 4 | root_url: https://billp.github.io/TermiNetwork/ 5 | module: TermiNetwork 6 | output: docs 7 | theme: fullwidth 8 | xcodebuild_arguments: [-scheme, 'TermiNetwork'] 9 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | type_body_length: 2 | - 300 3 | file_length: 4 | - 500 5 | function_body_length: 6 | - 50 -------------------------------------------------------------------------------- /Examples/Communication/DemoEnvironment.swift: -------------------------------------------------------------------------------- 1 | // DemoEnvironment.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | enum DemoEnvironment: EnvironmentProtocol { 24 | case production 25 | 26 | func configure() -> Environment { 27 | switch self { 28 | case .production: 29 | return TermiNetwork.Environment(scheme: .https, 30 | host: "terminetwork-backend.billp.dev", 31 | configuration: defaultConfiguration) 32 | } 33 | } 34 | 35 | private var defaultConfiguration: Configuration { 36 | let configuration = Configuration() 37 | configuration.keyDecodingStrategy = .convertFromSnakeCase 38 | configuration.verbose = true 39 | configuration.interceptors = [ServiceErrorInterceptor.self] 40 | if let path = Bundle.main.path(forResource: "MockData", ofType: "bundle") { 41 | configuration.mockDataBundle = Bundle(path: path) 42 | } 43 | return configuration 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Examples/Communication/Interceptors/ServiceErrorInterceptor.swift: -------------------------------------------------------------------------------- 1 | // GlobalNetworkErrorHandler.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import UIKit 22 | import TermiNetwork 23 | 24 | final class ServiceErrorInterceptor: InterceptorProtocol { 25 | let retryDelay: TimeInterval = 0 26 | 27 | func requestFinished(responseData data: Data?, 28 | error: TNError?, 29 | request: Request, 30 | proceed: @escaping (InterceptionAction) -> Void) { 31 | 32 | // Show a retry dialog on network error and on server error 500 33 | switch error { 34 | case .networkError(let error): 35 | showRetryDialog(errorMessage: error.localizedDescription) { 36 | proceed(.retry(delay: self.retryDelay)) 37 | } 38 | case .notSuccess(let statusCode, _): 39 | if statusCode / 500 == 1 { 40 | showRetryDialog( 41 | errorMessage: NSLocalizedString(String(format: "Server Error (%i) please try again.", statusCode), 42 | comment: "")) { 43 | proceed(.retry(delay: self.retryDelay)) 44 | } 45 | } else { 46 | proceed(.continue) 47 | } 48 | default: 49 | proceed(.continue) 50 | } 51 | } 52 | 53 | func showRetryDialog(errorMessage: String, retryAction: @escaping () -> Void) { 54 | DispatchQueue.main.async { 55 | let alert = UIAlertController(title: NSLocalizedString("Something went wrong", comment: ""), 56 | message: errorMessage, 57 | preferredStyle: .alert) 58 | alert.addAction(UIAlertAction(title: "Retry", style: .default, handler: { _ in 59 | retryAction() 60 | })) 61 | alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil)) 62 | if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { 63 | scene.windows.first?.rootViewController?.present(alert, animated: false, completion: nil) 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Examples/Communication/Models/Domain/City.swift: -------------------------------------------------------------------------------- 1 | // City.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct City: Identifiable { 23 | let id: UUID 24 | let cityID: Int 25 | let name: String 26 | let description: String? 27 | let countryName: String 28 | let thumb: String? 29 | let image: String? 30 | } 31 | -------------------------------------------------------------------------------- /Examples/Communication/Models/Domain/EncryptedModel.swift: -------------------------------------------------------------------------------- 1 | // EncryptedModel.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct EncryptedModel { 23 | var text: String 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Communication/Models/Domain/FileResponse.swift: -------------------------------------------------------------------------------- 1 | // FileResponse.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct FileResponse { 23 | var success: Bool 24 | var checksum: String 25 | } 26 | -------------------------------------------------------------------------------- /Examples/Communication/Models/Rest/RSCity.swift: -------------------------------------------------------------------------------- 1 | // RSCity.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct RSCity: Codable { 23 | let id: Int 24 | let name: String 25 | let description: String? 26 | let countryName: String 27 | let thumb: String? 28 | let image: String? 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Examples/Communication/Models/Rest/RSEncryptedModel.swift: -------------------------------------------------------------------------------- 1 | // RSEncryptedModel.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | class RSEncryptedModel: Codable { 23 | let value: String 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Communication/Models/Rest/RSFileResponse.swift: -------------------------------------------------------------------------------- 1 | // RSFileResponse.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | class RSFileResponse: Codable { 23 | var success: Bool 24 | var checksum: String 25 | } 26 | -------------------------------------------------------------------------------- /Examples/Communication/Repositories/CitiesRepository.swift: -------------------------------------------------------------------------------- 1 | // CitiesRepository.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | enum CitiesRepository: EndpointProtocol { 24 | case cities 25 | case city(id: Int) 26 | case thumb(city: City) 27 | case image(city: City) 28 | case pinning(configuration: Configuration) 29 | 30 | func configure() -> EndpointConfiguration { 31 | switch self { 32 | case .cities: 33 | return .init(method: .get, 34 | path: .path(["cities"]), 35 | mockFilePath: .path(["Cities", "cities.json"])) 36 | case .city(let id): 37 | return .init(method: .get, 38 | path: .path(["city", String(id)]), 39 | mockFilePath: .path(["Cities", "Details", String(format: "%i.json", id)])) 40 | case .thumb(let city): 41 | return .init(method: .get, 42 | path: .path([city.thumb ?? ""]), 43 | mockFilePath: .path(["Cities", "Thumbs", String(format: "%i.jpg", city.cityID)])) 44 | case .image(let city): 45 | return .init(method: .get, 46 | path: .path([city.image ?? ""]), 47 | mockFilePath: .path(["Cities", "Images", String(format: "%i.jpg", city.cityID)])) 48 | case .pinning(let configuration): 49 | return .init(method: .get, 50 | path: .path(["cities"]), 51 | configuration: configuration 52 | ) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/Communication/Repositories/MiscRepository.swift: -------------------------------------------------------------------------------- 1 | // MiscRepository.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | enum MiscRepository: EndpointProtocol { 24 | case testEncryptParams(param: String) 25 | case upload(fileUrl: URL) 26 | 27 | func configure() -> EndpointConfiguration { 28 | switch self { 29 | case .testEncryptParams(let value): 30 | return EndpointConfiguration( 31 | method: .post, 32 | path: .path(["test_encrypt_params"]), 33 | params: ["value": value] 34 | ) 35 | case .upload(fileUrl: let fileUrl): 36 | return EndpointConfiguration( 37 | method: .post, 38 | path: .path(["file_upload"]), 39 | params: ["file": MultipartFormDataPartType.url(fileUrl)] 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Examples/Communication/Transformers/CitiesTransformer.swift: -------------------------------------------------------------------------------- 1 | // CitiesTransformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | final class CitiesTransformer: Transformer<[RSCity], [City]> { 24 | override func transform(_ object: [RSCity]) throws -> [City] { 25 | object.map { rsCity in 26 | City(id: UUID(), 27 | cityID: rsCity.id, 28 | name: rsCity.name, 29 | description: rsCity.description, 30 | countryName: rsCity.countryName, 31 | thumb: rsCity.thumb, 32 | image: rsCity.image) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Examples/Communication/Transformers/CityTransformer.swift: -------------------------------------------------------------------------------- 1 | // CityTransformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | class CityTransformer: Transformer { 24 | override func transform(_ object: RSCity) throws -> City { 25 | City(id: UUID(), 26 | cityID: object.id, 27 | name: object.name, 28 | description: object.description, 29 | countryName: object.countryName, 30 | thumb: object.thumb, 31 | image: object.image) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Examples/Communication/Transformers/EncryptedModelTransformer.swift: -------------------------------------------------------------------------------- 1 | // EncryptedModelTransformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | class EncryptedModelTransformer: Transformer { 24 | override func transform(_ object: RSEncryptedModel) throws -> EncryptedModel { 25 | return EncryptedModel(text: object.value) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Communication/Transformers/FileUploadTransformer.swift: -------------------------------------------------------------------------------- 1 | // FileUploadTransformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | class FileUploadTransformer: Transformer { 24 | override func transform(_ object: RSFileResponse) throws -> FileResponse { 25 | return FileResponse(success: object.success, 26 | checksum: object.checksum) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Screens/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // AppDelegate.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import UIKit 22 | import TermiNetwork 23 | 24 | class AppDelegate: NSObject, UIApplicationDelegate { 25 | func application(_ application: UIApplication, 26 | didFinishLaunchingWithOptions 27 | launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 28 | 29 | Environment.set(DemoEnvironment.production) 30 | return true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Examples/Screens/ContentView.swift: -------------------------------------------------------------------------------- 1 | // ContentView.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import SwiftUI 21 | import Combine 22 | import TermiNetwork 23 | 24 | struct DemoAppRow: View { 25 | var app: DemoApp 26 | 27 | var body: some View { 28 | VStack(alignment: .leading, spacing: 5) { 29 | Text(app.name).font(.system(size: 18)) 30 | Text(app.description).font(.system(size: 15)) 31 | } 32 | } 33 | } 34 | 35 | struct ContentView: View { 36 | var body: some View { 37 | NavigationView { 38 | List(DemoApp.Apps) { app in 39 | NavigationLink(destination: app.destination) { 40 | DemoAppRow(app: app) 41 | } 42 | } 43 | .navigationBarTitleDisplayMode(.inline) 44 | .toolbar(content: { 45 | ToolbarItem(placement: .principal, content: { 46 | Text("TermiNetwork").bold() 47 | }) 48 | }) 49 | .onAppear { 50 | Environment.current.configuration?.mockDataEnabled = false 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Examples/Screens/DemoApp.swift: -------------------------------------------------------------------------------- 1 | // DemoApp.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import SwiftUI 22 | 23 | struct DemoApp: Identifiable { 24 | var id = UUID() 25 | var name: String 26 | var description: String 27 | var destination: AnyView 28 | 29 | @MainActor 30 | static var Apps: [DemoApp] { 31 | [ 32 | DemoApp( 33 | name: "City Explorer", 34 | description: "Repository, Transformers, Codables", 35 | destination: AnyView(CityExplorerView(viewModel: .init(usesMockData: false))) 36 | ), 37 | DemoApp( 38 | name: "City Explorer - Offline Mode", 39 | description: "Repository, Transformers, Codables, Mock Data", 40 | destination: AnyView(CityExplorerView(viewModel: .init(usesMockData: true))) 41 | ), 42 | DemoApp( 43 | name: "Certificate Pinning", 44 | description: "Man-in-the-middle attack prevention", 45 | destination: AnyView(CertificatePinningView()) 46 | ), 47 | DemoApp( 48 | name: "Encrypted Communication", 49 | description: "Crypto Middleware", 50 | destination: AnyView(EncryptedCommunicationView()) 51 | ), 52 | DemoApp( 53 | name: "Reachability", 54 | description: "Monitor network state changes", 55 | destination: AnyView(Reachability()) 56 | ), 57 | DemoApp( 58 | name: "File Uploader", 59 | description: "Upload files with progress", 60 | destination: AnyView(FileUploader(viewModel: .init())) 61 | ), 62 | DemoApp( 63 | name: "File Downloader", 64 | description: "Download files with progress", 65 | destination: AnyView(FileDownloader()) 66 | ) 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Examples/Screens/Encrypted Communication/EncryptedCommunicationView.swift: -------------------------------------------------------------------------------- 1 | // EncryptedCommunicationView.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import SwiftUI 22 | import TermiNetwork 23 | 24 | struct EncryptedCommunicationView: View { 25 | @State var encryptionKey: String = CryptoMiddleware.encryptionKey 26 | @State var decryptionKey: String = CryptoMiddleware.decryptionKey 27 | @State var text: String = "Hello!!!" 28 | @State var responseString: String = "Press Start Request..." 29 | 30 | var configuration: Configuration { 31 | let configuration = Configuration() 32 | configuration.requestBodyType = .JSON 33 | configuration.requestMiddleware = [CryptoMiddleware.self] 34 | return configuration 35 | } 36 | 37 | let textFieldBackgroundColor = Color(.sRGB, red: 0.922, green: 0.922, blue: 0.922, opacity: 1.0) 38 | 39 | var body: some View { 40 | VStack { 41 | UIHelpers.fieldLabel("Encryption Key") 42 | UIHelpers.customTextField("Encryption key", text: $encryptionKey, onChange: { val in 43 | CryptoMiddleware.encryptionKey = val 44 | }) 45 | UIHelpers.fieldLabel("Decryption Key") 46 | UIHelpers.customTextField("Decryption key", text: $decryptionKey, onChange: { val in 47 | CryptoMiddleware.decryptionKey = val 48 | }) 49 | UIHelpers.fieldLabel("Text") 50 | UIHelpers.customTextField("Value", text: $text) 51 | UIHelpers.fieldLabel("Response (if the decryption succeeds, it will show same value as in 'Text' field).") 52 | TextEditor(text: $responseString) 53 | .font(.footnote) 54 | .background(textFieldBackgroundColor) 55 | .cornerRadius(5) 56 | .clipped() 57 | UIHelpers.button("Start Request", action: startRequest) 58 | .padding(.bottom, 20) 59 | 60 | } 61 | .padding([.leading, .trailing, .top], 20) 62 | .navigationTitle("Encryption Layer") 63 | } 64 | 65 | // MARK: Communication 66 | 67 | func startRequest() { 68 | responseString = "fetching..." 69 | 70 | Client(configuration: configuration) 71 | .request(for: .testEncryptParams(param: text)) 72 | .success(transformer: EncryptedModelTransformer.self) { model in 73 | responseString = model.text 74 | } 75 | .failure { error in 76 | responseString = error.localizedDescription ?? "" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Examples/Screens/File Uploader/FileUploaderUtils.swift: -------------------------------------------------------------------------------- 1 | // FileUploaderUtils.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import CommonCrypto 22 | 23 | class FileUploaderUtils { 24 | static func sha256(url: URL) -> String? { 25 | do { 26 | let bufferSize = 1024 * 1024 27 | // Open file for reading: 28 | let file = try FileHandle(forReadingFrom: url) 29 | defer { 30 | file.closeFile() 31 | } 32 | 33 | // Create and initialize SHA256 context: 34 | var context = CC_SHA256_CTX() 35 | CC_SHA256_Init(&context) 36 | 37 | // Read up to `bufferSize` bytes, until EOF is reached, and update SHA256 context: 38 | while autoreleasepool(invoking: { 39 | // Read up to `bufferSize` bytes 40 | let data = file.readData(ofLength: bufferSize) 41 | if data.count > 0 { 42 | data.withUnsafeBytes { buffer in 43 | let memoryOffset = buffer.bindMemory(to: UInt8.self).baseAddress! 44 | CC_SHA256_Update(&context, memoryOffset, numericCast(data.count)) 45 | } 46 | // Continue 47 | return true 48 | } else { 49 | // End of file 50 | return false 51 | } 52 | }) { } 53 | 54 | // Compute the SHA256 digest: 55 | var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH)) 56 | digest.withUnsafeMutableBytes { buffer in 57 | let memoryOffset = buffer.bindMemory(to: UInt8.self).baseAddress! 58 | _ = CC_SHA256_Final(memoryOffset, &context) 59 | } 60 | return digest.map { String(format: "%02hhx", $0) }.joined() 61 | } catch { 62 | print(error) 63 | return nil 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Examples/Screens/Pinning/CertificatePinningView.swift: -------------------------------------------------------------------------------- 1 | // CertificatePinningView.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import SwiftUI 22 | import TermiNetwork 23 | 24 | struct CertificatePinningView: View { 25 | @State var isCertificateValid: Bool = true 26 | @State var responseString: String = "Press Start Request..." 27 | 28 | init() { 29 | UITextView.appearance().backgroundColor = .clear 30 | } 31 | 32 | var body: some View { 33 | VStack { 34 | TextEditor(text: $responseString) 35 | .padding(4) 36 | .background(Color(.sRGB, red: 0.922, green: 0.922, blue: 0.922, opacity: 1.0)) 37 | .cornerRadius(5) 38 | .font(.footnote) 39 | .clipped() 40 | 41 | Toggle("Valid Certificate", isOn: $isCertificateValid) 42 | .font(.footnote) 43 | .padding(.top, 10) 44 | .padding(.bottom, 50) 45 | Spacer() 46 | UIHelpers.button("Start Request", action: startRequest) 47 | .padding(.bottom, 20) 48 | } 49 | .padding([.leading, .trailing, .top], 20) 50 | .navigationTitle("Certificate Pinning") 51 | .onAppear { 52 | Environment.current.configuration?.mockDataEnabled = false 53 | } 54 | } 55 | 56 | func startRequest() { 57 | let certificateName = isCertificateValid ? "terminetwork.billp.dev" : "forums.swift.org" 58 | 59 | let configuration = Configuration() 60 | guard let certUrlPath = Bundle.main.path(forResource: certificateName, ofType: "cer") else { 61 | return 62 | } 63 | configuration.certificatePaths = [certUrlPath] 64 | 65 | responseString = "fetching..." 66 | 67 | Client() 68 | .request(for: .pinning(configuration: configuration)) 69 | .success(responseType: Data.self) { response in 70 | responseString = response.toJSONString() ?? "" 71 | } 72 | .failure { error in 73 | responseString = error.localizedDescription ?? "" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Examples/Screens/Reachability/Reachability.swift: -------------------------------------------------------------------------------- 1 | // Reachability.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import SwiftUI 22 | import TermiNetwork 23 | 24 | struct Reachability: View { 25 | @StateObject var viewModel: ViewModel = .init() 26 | 27 | var body: some View { 28 | VStack { 29 | HStack { 30 | Text("Network state:") 31 | Text(viewModel.status) 32 | } 33 | } 34 | .padding([.leading, .trailing, .top], 20) 35 | .navigationTitle("Reachability") 36 | .onDisappear { [weak viewModel] in 37 | viewModel?.onDisappear() 38 | } 39 | } 40 | } 41 | 42 | extension Reachability { 43 | @MainActor 44 | class ViewModel: ObservableObject { 45 | @Published var status: String = "" 46 | 47 | private var reachability: TermiNetwork.Reachability? = TermiNetwork.Reachability(hostname: "google.com") 48 | 49 | init() { 50 | startMonitoringState() 51 | } 52 | 53 | private func startMonitoringState() { 54 | try? reachability?.monitorState { [weak self] state in 55 | self?.status = String(describing: state) 56 | } 57 | } 58 | 59 | private func stopMonitoringState() { 60 | reachability?.stopMonitoring() 61 | } 62 | 63 | func onDisappear() { 64 | stopMonitoringState() 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Examples/Screens/TermiNetworkExamplesApp.swift: -------------------------------------------------------------------------------- 1 | // TermiNetworkExamplesApp.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import SwiftUI 21 | import TermiNetwork 22 | 23 | // swiftlint:disable weak_delegate 24 | 25 | @main 26 | struct TermiNetworkExamplesApp: App { 27 | @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 28 | 29 | var body: some Scene { 30 | WindowGroup { 31 | ContentView() 32 | } 33 | } 34 | } 35 | 36 | // swiftlint:enable weak_delegate 37 | -------------------------------------------------------------------------------- /Examples/Screens/UIHelpers.swift: -------------------------------------------------------------------------------- 1 | // UIHelpers.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import SwiftUI 22 | 23 | class UIHelpers { 24 | static let textFieldBackgroundColor = Color(.sRGB, red: 0.922, green: 0.922, blue: 0.922, opacity: 1.0) 25 | 26 | static func fieldLabel(_ title: String) -> some View { 27 | Text(title) 28 | .font(.caption) 29 | .bold() 30 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) 31 | } 32 | 33 | static func customTextField(_ title: String, 34 | text: Binding, 35 | onChange: ((String) -> Void)? = nil) -> some View { 36 | TextField(title, 37 | text: text, 38 | onEditingChanged: { _ in onChange?(text.wrappedValue) }) 39 | .padding(5) 40 | .background(textFieldBackgroundColor) 41 | .font(.footnote) 42 | .cornerRadius(3.0) 43 | .clipped() 44 | } 45 | 46 | static func button(_ title: String, action: @escaping () -> Void) -> some View { 47 | Button(action: action) { 48 | Text(title) 49 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .center) 50 | .foregroundColor(.white) 51 | } 52 | .padding(10) 53 | .background(Color.blue) 54 | .cornerRadius(5) 55 | .clipped() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-72.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small-50.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/icon@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/ios-marketing.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/TerrmiNetworkSplash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "TermiNetworkLogo-2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/Supporting Files/Assets.xcassets/TerrmiNetworkSplash.imageset/TermiNetworkLogo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/Assets.xcassets/TerrmiNetworkSplash.imageset/TermiNetworkLogo-2.png -------------------------------------------------------------------------------- /Examples/Supporting Files/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIImageName 33 | TermiNetworkLogo2 34 | 35 | UILaunchStoryboardName 36 | Launch Screen 37 | UIRequiredDeviceCapabilities 38 | 39 | armv7 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Details/23.json: -------------------------------------------------------------------------------- 1 | {"id":23,"name":"Paris","description":"Paris is the capital and most populous city of France, with a population of 2,148,271 residents (official estimate, 1 January 2020) in an area of 105 square kilometres (41 square miles).[1] Since the 17th century, Paris has been one of Europe's major centres of finance, diplomacy, commerce, fashion, science and arts. The City of Paris is the centre and seat of government of the Île-de-France, or Paris Region, which has an estimated official 2020 population of 12,278,210, or about 18 percent of the population of France. The Paris Region had a GDP of €709 billion ($808 billion) in 2017. According to the Economist Intelligence Unit Worldwide Cost of Living Survey in 2018, Paris was the second most expensive city in the world, after Singapore, and ahead of Zürich, Hong Kong, Oslo and Geneva.[4] Another source ranked Paris as most expensive, on a par with Singapore and Hong Kong, in 2018.","country_name":"France","image":"/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBJdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--9145363fe9db5685e247513778be063a5e91a5eb/paris.jpg"} -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Details/24.json: -------------------------------------------------------------------------------- 1 | {"id":24,"name":"Athens","description":"Athens is the historical capital of Europe, with a long history, dating from the first settlement in the Neolithic age. In the 5th Century BC (the “Golden Age of Pericles”) – the culmination of Athens’ long, fascinating history – the city’s values and civilization acquired a universal significance. Over the years, a multitude of conquerors occupied Athens, and erected unique, splendid monuments - a rare historical palimpsest. In 1834, it became the capital of the modern Greek state and in two centuries since it has become an attractive modern metropolis with unrivalled charm.","country_name":"Greece","image":"/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--687bfb28cf56ed645dce2477dd4f1dba75a6622b/athens.jpg"} -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Details/25.json: -------------------------------------------------------------------------------- 1 | {"id":25,"name":"Yokohama","description":"Yokohama, city and port, capital of Kanagawa ken (prefecture), east-central Honshu, Japan. The second most populous city in the country, it is a major component of the Tokyo-Yokohama metropolitan area, the largest urban agglomeration in Japan.","country_name":"Japan","image":"/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--cb7226c89fcff3cccc6e1dd572ffa2bd9e25199a/yokohama.jpg"} -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Details/26.json: -------------------------------------------------------------------------------- 1 | {"id":26,"name":"Cairo","description":"Cairo, Arabic Al-Qāhirah (“The Victorious”), city, capital of Egypt, and one of the largest cities in Africa. Cairo has stood for more than 1,000 years on the same site on the banks of the Nile, primarily on the eastern shore, some 500 miles (800 km) downstream from the Aswān High Dam. Located in the northeast of the country, Cairo is the gateway to the Nile delta, where the lower Nile separates into the Rosetta and Damietta branches. Metropolitan Cairo is made up of the Cairo muḥāfazah (governorate), as well as other districts, some of which belong to neighbouring governorates such as Al-Jīzah and Qalūbiyyah. Area governorate, 83 square miles (214 square km). Pop. (2006) governorate, 7,786,640; (2005 est.) urban agglom., 11,128,000..","country_name":"Egypt","image":"/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--91a7d99dcae930b2abf5d94b3386cb7560d7bf74/cairo.jpeg"} -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Details/27.json: -------------------------------------------------------------------------------- 1 | {"id":27,"name":"Barcelona","description":"Barcelona is the capital city of Catalonia, which is a region of Spain. Barcelona is the largest city on the Mediterranean coast. The city is between the rivers of Llobregat and Besòs, and south of the Pyrenees mountains. It has a hot-summer Mediterranean climate (Csa in the Koeppen climate classification). In 1992, Barcelona hosted the Summer Olympic Games. Many new parks were opened and other significant changes to the city were made. One example is opening the new beaches in the Poble Nou area. In 2007, about 1.6 million people lived in Barcelona. Around 3.1 million people live in the Metropolitan Area and 4.9 million people live in the Urban Region. Barcelona is the second most populated city in Spain, and the tenth in the European Union. Barcelona is home to football team F.C. Barcelona.","country_name":"Spain","image":"/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4a0973405f17ea8d1cfff5c0bfed4ebd5b1433f3/barcelona.jpg"} -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Details/28.json: -------------------------------------------------------------------------------- 1 | {"id":28,"name":"Prague","description":"Prague, capital city of the Czech Republic, is bisected by the Vltava River. Nicknamed “the City of a Hundred Spires,” it's known for its Old Town Square, the heart of its historic core, with colorful baroque buildings, Gothic churches and the medieval Astronomical Clock, which gives an animated hourly show. Completed in 1402, pedestrian Charles Bridge is lined with statues of Catholic saints.","country_name":"Czech Republic","image":"/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBLQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--de5cb9417f562c98aacac7f9fbed641ca0b945a5/prague.jpeg"} -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Images/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Images/23.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Images/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Images/24.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Images/25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Images/25.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Images/26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Images/26.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Images/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Images/27.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Images/28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Images/28.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Thumbs/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Thumbs/23.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Thumbs/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Thumbs/24.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Thumbs/25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Thumbs/25.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Thumbs/26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Thumbs/26.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Thumbs/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Thumbs/27.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/Thumbs/28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Examples/Supporting Files/MockData.bundle/Cities/Thumbs/28.jpg -------------------------------------------------------------------------------- /Examples/Supporting Files/MockData.bundle/Cities/cities.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id" : 24, 4 | "thumb" : "\/rails\/active_storage\/representations\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--687bfb28cf56ed645dce2477dd4f1dba75a6622b\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lOTkRBd2VEUXdNRDRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--ab4a1a59a129eb2f2f03a2094b1137feef6c38d4\/athens.jpg", 5 | "name" : "Athens", 6 | "country_name" : "Greece" 7 | }, 8 | { 9 | "id" : 25, 10 | "thumb" : "\/rails\/active_storage\/representations\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--cb7226c89fcff3cccc6e1dd572ffa2bd9e25199a\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lOTkRBd2VEUXdNRDRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--ab4a1a59a129eb2f2f03a2094b1137feef6c38d4\/yokohama.jpg", 11 | "name" : "Yokohama", 12 | "country_name" : "Japan" 13 | }, 14 | { 15 | "id" : 23, 16 | "thumb" : "\/rails\/active_storage\/representations\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBJdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--9145363fe9db5685e247513778be063a5e91a5eb\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lOTkRBd2VEUXdNRDRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--ab4a1a59a129eb2f2f03a2094b1137feef6c38d4\/paris.jpg", 17 | "name" : "Paris", 18 | "country_name" : "France" 19 | }, 20 | { 21 | "id" : 26, 22 | "thumb" : "\/rails\/active_storage\/representations\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--91a7d99dcae930b2abf5d94b3386cb7560d7bf74\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lOTkRBd2VEUXdNRDRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--ab4a1a59a129eb2f2f03a2094b1137feef6c38d4\/cairo.jpeg", 23 | "name" : "Cairo", 24 | "country_name" : "Egypt" 25 | }, 26 | { 27 | "id" : 27, 28 | "thumb" : "\/rails\/active_storage\/representations\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBKdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4a0973405f17ea8d1cfff5c0bfed4ebd5b1433f3\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lOTkRBd2VEUXdNRDRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--ab4a1a59a129eb2f2f03a2094b1137feef6c38d4\/barcelona.jpg", 29 | "name" : "Barcelona", 30 | "country_name" : "Spain" 31 | }, 32 | { 33 | "id" : 28, 34 | "thumb" : "\/rails\/active_storage\/representations\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBLQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--de5cb9417f562c98aacac7f9fbed641ca0b945a5\/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9MY21WemFYcGxTU0lOTkRBd2VEUXdNRDRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--ab4a1a59a129eb2f2f03a2094b1137feef6c38d4\/prague.jpeg", 35 | "name" : "Prague", 36 | "country_name" : "Czech Republic" 37 | } 38 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2018-2021 Vasilis Panagiotopoulos. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in the 5 | Software without restriction, including without limitation the rights to use, copy, 6 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies 10 | or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 13 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 14 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 15 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 16 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "TermiNetwork", 6 | platforms: [ 7 | .macOS(.v10_15), 8 | .iOS(.v14), 9 | .tvOS(.v14), 10 | .watchOS(.v6) 11 | ], 12 | products: [ 13 | .library( 14 | name: "TermiNetwork", 15 | targets: ["TermiNetwork"]) 16 | ], 17 | targets: [ 18 | .target( 19 | name: "TermiNetwork", 20 | path: "Source") 21 | ], 22 | swiftLanguageVersions: [.v5] 23 | ) 24 | -------------------------------------------------------------------------------- /Scripts/generate_changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | github_changelog_generator -u billp -p TermiNetwork --since-tag 1.0.0 3 | -------------------------------------------------------------------------------- /Scripts/generate_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | jazzy --module TermiNetwork --theme fullwidth --xcodebuild-arguments -scheme,'TermiNetwork' 4 | -------------------------------------------------------------------------------- /Source/Cache.swift: -------------------------------------------------------------------------------- 1 | // Cache.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Cache is used internally for various tasks such as in-memory caching image data. 23 | /// Primarily used in UIImageView|NSImageView|WKInterfaceImage and Image (SwiftUI) helpers. 24 | public final class Cache { 25 | /// Singleton object. 26 | public static let shared = Cache() 27 | 28 | /// Singleton definition 29 | let cache: NSCache = NSCache() 30 | 31 | /// Configures the cache. 32 | /// - Parameters: 33 | /// - countLimit: The maximum number of objects the cache will hold. 34 | /// - size: The maximum total size (in bytes) the cache will hold before it starts removing objects. 35 | public func configureCache(countLimit: Int, size: Int) { 36 | cache.countLimit = countLimit 37 | cache.totalCostLimit = size 38 | } 39 | 40 | /// Clears cache. 41 | public func clearCache() { 42 | cache.removeAllObjects() 43 | } 44 | 45 | /// :nodoc: 46 | subscript(key: String) -> Data? { 47 | get { 48 | cache.object(forKey: key as NSString) as Data? 49 | } 50 | set { 51 | guard let data = newValue as NSData? else { 52 | return 53 | } 54 | cache.setObject(data, forKey: key as NSString) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Source/CertificatePinningManager.swift: -------------------------------------------------------------------------------- 1 | // CertificatePinningManager.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | class CertificatePinningManager { 23 | var challenge: URLAuthenticationChallenge 24 | var completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void 25 | 26 | weak var request: Request? 27 | 28 | init(challenge: URLAuthenticationChallenge, 29 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void, 30 | request: Request?) { 31 | self.challenge = challenge 32 | self.completionHandler = completionHandler 33 | self.request = request 34 | } 35 | 36 | func handleDidReceiveChallenge() { 37 | var challengeDisposition: URLSession.AuthChallengeDisposition = .cancelAuthenticationChallenge 38 | guard let serverTrust = challenge.protectionSpace.serverTrust else { 39 | completionHandler(.performDefaultHandling, nil) 40 | return 41 | } 42 | DispatchQueue.global(qos: .background).async { 43 | if let certData = self.request?.configuration.certificateData, 44 | let remoteCert = SecTrustGetCertificateAtIndex(serverTrust, 0) { 45 | let policies = NSMutableArray() 46 | policies.add(SecPolicyCreateSSL(true, (self.challenge.protectionSpace.host as CFString))) 47 | SecTrustSetPolicies(serverTrust, policies) 48 | 49 | // Evaluate server certificate 50 | var error: CFError? 51 | let isServerTrusted = SecTrustEvaluateWithError(serverTrust, &error) 52 | 53 | let remoteCertificateData: NSData = SecCertificateCopyData(remoteCert) 54 | if isServerTrusted && error == nil && certData.contains(remoteCertificateData) { 55 | challengeDisposition = .useCredential 56 | } else { 57 | self.request?.pinningErrorOccured = true 58 | } 59 | } else { 60 | challengeDisposition = .performDefaultHandling 61 | } 62 | 63 | DispatchQueue.main.async { 64 | self.completionHandler(challengeDisposition, 65 | URLCredential(trust: serverTrust)) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Source/Client.swift: -------------------------------------------------------------------------------- 1 | // Client.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// This class is used to create instances of Repository that can be used to start requests based on the given endpoint. 23 | public final class Client { 24 | // MARK: Properties 25 | fileprivate var environment: Environment? 26 | 27 | /// Repository configuration 28 | public var configuration: Configuration? 29 | 30 | /// Initialize with environment that overrides the one set by Environment.set(_). 31 | public init(environment: EnvironmentProtocol? = nil, 32 | configuration: Configuration? = nil) { 33 | self.environment = environment?.configure() ?? Environment.current 34 | self.configuration = configuration 35 | } 36 | 37 | /// Returns a Request that can be used later, e.g. for starting the request in a later time or canceling it. 38 | /// 39 | /// - parameters: 40 | /// - endpoint: a RepositoryProtocol enum value. 41 | public func request(for endpoint: Repository) -> Request { 42 | Request(endpoint: endpoint, 43 | environment: environment, 44 | configuration: configuration) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/Enums/EnvironmentType.swift: -------------------------------------------------------------------------------- 1 | // EnvironmentType.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | internal enum EnvironmentType { 23 | case normal(scheme: URLScheme, host: String, port: Int?, suffix: Path?) 24 | case url(String) 25 | } 26 | -------------------------------------------------------------------------------- /Source/Enums/HttpMethod.swift: -------------------------------------------------------------------------------- 1 | // HttpMethod.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// The HTTP request method based on specification of https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html. 23 | public enum HttpMethod: String { 24 | /// GET request method. 25 | case get 26 | /// HEAD request method. 27 | case head 28 | /// POST request method. 29 | case post 30 | /// PUT request method. 31 | case put 32 | /// DELETE request method. 33 | case delete 34 | /// CONNECT request method. 35 | case connect 36 | /// OPTIONS request method. 37 | case options 38 | /// TRACE request method. 39 | case trace 40 | /// PATCH request method. 41 | case patch 42 | } 43 | -------------------------------------------------------------------------------- /Source/Enums/InterceptionAction.swift: -------------------------------------------------------------------------------- 1 | // InterceptionAction.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// This will be used in interceptor callback as an action to inteceptors chain. 23 | public enum InterceptionAction { 24 | /// Continue with the next interceptor or final callbacks if there is no other interceptor in chain. 25 | case `continue` 26 | /// Retry the request 27 | /// - Parameters 28 | /// - delay: The delay between retries in seconds. Pass nil value for no delay. 29 | case retry(delay: TimeInterval?) 30 | } 31 | -------------------------------------------------------------------------------- /Source/Enums/Path.swift: -------------------------------------------------------------------------------- 1 | // Path.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// URL path representation based on String components. 23 | public enum Path { 24 | // MARK: Public properties 25 | 26 | /// Returns the constructed path as String based on .path components. 27 | public var convertedPath: String { 28 | switch self { 29 | case .path(let components): 30 | return components.joined(separator: "/") 31 | } 32 | } 33 | 34 | // MARK: Public methods 35 | 36 | /// An enum case that can be used where path is needed. For example: .path(["user", "1", "details"]). 37 | /// Later you can call covertedPath to construct the path as String (e.g. /user/1/details) 38 | case path(_ components: [String]) 39 | } 40 | -------------------------------------------------------------------------------- /Source/Enums/PathType.swift: -------------------------------------------------------------------------------- 1 | // PathType.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// The type of the path specified in request construction methods. 23 | internal enum PathType: Error { 24 | case relative 25 | case absolute 26 | } 27 | -------------------------------------------------------------------------------- /Source/Enums/QueueFailureMode.swift: -------------------------------------------------------------------------------- 1 | // QueueFailureMode.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Type that specifies the behavior of the queue when a request fails 23 | public enum QueueFailureMode { 24 | /// Cancels the execution of the queue after a request (operation) fails 25 | case cancelAll 26 | /// Continues the execution of the queue after a request (operation) fails 27 | case `continue` 28 | } 29 | -------------------------------------------------------------------------------- /Source/Enums/RequestBodyType.swift: -------------------------------------------------------------------------------- 1 | // RequestBodyType.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// The body type of the request 23 | public enum RequestBodyType: Equatable { 24 | /// The request params are sent as application/x-www-form-urlencoded mime type 25 | case xWWWFormURLEncoded 26 | /// The request params are sent as application/json mime type 27 | case JSON 28 | /// Type for multipart/form-data body by giving the boundary as String. Typically you don't have to set it manually 29 | /// since it is set automatically by upload requests. 30 | case multipartFormData(boundary: String) 31 | 32 | func value() -> String { 33 | switch self { 34 | case .xWWWFormURLEncoded: 35 | return "application/x-www-form-urlencoded" 36 | case .JSON: 37 | return "application/json" 38 | case .multipartFormData(let boundary): 39 | return "multipart/form-data; boundary=\(boundary)" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/Enums/RequestType.swift: -------------------------------------------------------------------------------- 1 | // RequestType.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Internal type for figuring out the type of the request 23 | internal enum RequestType { 24 | case data 25 | case upload 26 | case download(String) 27 | } 28 | -------------------------------------------------------------------------------- /Source/Enums/URLScheme.swift: -------------------------------------------------------------------------------- 1 | // URLScheme.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// The url scheme that will be used in an environment. 23 | public enum URLScheme: String { 24 | /// HTTP Schema. 25 | case http 26 | /// HTTPS Schema. 27 | case https 28 | } 29 | -------------------------------------------------------------------------------- /Source/Extensions/Data+Extensions.swift: -------------------------------------------------------------------------------- 1 | // Data+Extensions.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Data extension for JSON deserialization. 23 | public extension Data { 24 | /// 25 | /// Deserializes the JSON Data to the given type. 26 | /// - returns: The deserilized object. 27 | func deserializeJSONData( 28 | withKeyDecodingStrategy keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy? = nil) 29 | throws -> T where T: Decodable { 30 | let jsonDecoder = JSONDecoder() 31 | if let keyDecodingStrategy = keyDecodingStrategy { 32 | jsonDecoder.keyDecodingStrategy = keyDecodingStrategy 33 | } 34 | return try jsonDecoder.decode(T.self, from: self) 35 | } 36 | 37 | /// 38 | /// Creates a JSON string (pretty printed) from Data. 39 | /// - returns: The pretty printed string. 40 | func toJSONString() -> String? { 41 | if let dictionary = try? JSONSerialization.jsonObject(with: self, options: []) { 42 | if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, 43 | options: [.prettyPrinted, .withoutEscapingSlashes]), 44 | let jsonString = String(data: jsonData, encoding: .utf8) { 45 | return jsonString 46 | } 47 | } 48 | return nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Source/Extensions/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | // Dictionary+Extensions.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | extension Dictionary { 23 | internal func toJSONData() throws -> Data? { 24 | return try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) 25 | } 26 | 27 | internal func toJSONString() -> String? { 28 | return try? self.toJSONData()?.toJSONString() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/Extensions/Operations/Decodable+Transformer.swift: -------------------------------------------------------------------------------- 1 | // Decodable+Transformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | public protocol TransformerProtocol: NSObject { 23 | associatedtype FromType 24 | associatedtype ToType 25 | 26 | func transform(_ object: FromType) throws -> ToType 27 | } 28 | 29 | /// Inherit this class as to create your transformers. 30 | /// You should pass FromType and ToType (generic types) in your subclass definition. 31 | open class Transformer: NSObject, TransformerProtocol { 32 | /// This is the default transform method. This method should be overriden by subclass 33 | /// 34 | /// - parameters: 35 | /// - object: The object that will be transformed 36 | /// - returns: The transformed object 37 | open func transform(_ object: FromType) throws -> ToType { 38 | fatalError("You must override this method.") 39 | } 40 | 41 | /// Default initializer 42 | required public override init() { } 43 | } 44 | 45 | /// Decodable extension for Transformers 46 | public extension Decodable { 47 | /// Transforms the decodable object with the specified transformer. 48 | /// 49 | /// - parameters: 50 | /// - transformer: The transformer object that handles the transformation. 51 | /// - returns: The transformed object 52 | func transform(with transformer: Transformer) throws -> ToType { 53 | guard let object = self as? FromType else { 54 | throw TNError.transformationFailed 55 | } 56 | return try transformer.transform(object) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Source/Extensions/Operations/Request+FileOperations.swift: -------------------------------------------------------------------------------- 1 | // Request+FileOperations.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | public extension Request { 23 | // MARK: Upload - Data 24 | 25 | /// Executed when the request is succeeded and the response is Data type. 26 | /// 27 | /// - parameters: 28 | /// - progressUpdate: specifies a progress callback to get upload progress updates. 29 | /// - returns: The Request object. 30 | @discardableResult 31 | func upload(progressUpdate: ProgressCallbackType? = nil) -> Request { 32 | checkUploadParamsType() 33 | 34 | self.requestType = .upload 35 | self.progressUpdate = progressUpdate 36 | 37 | return self 38 | } 39 | 40 | // MARK: Download 41 | 42 | /// Executed when the download request is succeeded. 43 | /// 44 | /// - parameters: 45 | /// - destinationPath: The destination file path to save the file. 46 | /// - progressUpdate: specifies a progress callback to get upload progress updates. 47 | /// - completionHandler: The completion handler with the Data object. 48 | /// - returns: The Request object. 49 | @discardableResult 50 | func download(destinationPath: String, 51 | progressUpdate: ProgressCallbackType?) -> Request { 52 | checkUploadParamsType() 53 | 54 | self.requestType = .download(destinationPath) 55 | self.progressUpdate = progressUpdate 56 | 57 | executeCurrentOperationIfNeeded() 58 | 59 | return self 60 | } 61 | 62 | /// Used internally to verify upload multipart/form-data params. 63 | /// 64 | /// - throws: TNError.uploadOperationInvalidParams. 65 | internal func checkUploadParamsType() { 66 | guard let params else { return } 67 | if !params.values.allSatisfy({ $0 is MultipartFormDataPartType }) { 68 | fatalError(""" 69 | "Upload param values should have a type of \(MultipartFormDataPartType.self). 70 | Refer to documentation for more details. 71 | """) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Source/Extensions/Request+Middleware.swift: -------------------------------------------------------------------------------- 1 | // Request+Middleware.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | extension Request { 23 | func shouldHandleMiddleware() -> Bool { 24 | guard let middleware = configuration.requestMiddleware else { 25 | return false 26 | } 27 | return middleware.count > 0 28 | } 29 | 30 | func handleMiddlewareParamsIfNeeded(params: [String: Any?]?) throws -> [String: Any?]? { 31 | guard shouldHandleMiddleware() else { 32 | return params 33 | } 34 | 35 | var newParams = params 36 | try configuration.requestMiddleware?.forEach { middleware in 37 | newParams = try middleware.init().processParams(with: newParams) 38 | } 39 | return newParams 40 | } 41 | 42 | func handleMiddlewareProcessResponseIfNeeded(responseData: Data?) throws -> Data? { 43 | guard shouldHandleMiddleware() else { 44 | return responseData 45 | } 46 | 47 | var newResponseData = responseData 48 | try configuration.requestMiddleware?.forEach { middleware in 49 | newResponseData = try middleware.init().processResponse(with: newResponseData) 50 | } 51 | return newResponseData 52 | } 53 | 54 | func handleMiddlewareHeadersBeforeSendIfNeeded(headers: [String: String]?) throws -> [String: String]? { 55 | guard shouldHandleMiddleware() else { 56 | return headers 57 | } 58 | 59 | var newHeaders = headers 60 | try configuration.requestMiddleware?.forEach { middleware in 61 | newHeaders = try middleware.init().processHeadersBeforeSend(with: newHeaders) 62 | } 63 | return newHeaders 64 | } 65 | 66 | func handleMiddlewareHeadersAfterReceiveIfNeeded(headers: [String: String]?) throws -> [String: String]? { 67 | guard shouldHandleMiddleware() else { 68 | return headers 69 | } 70 | 71 | var newHeaders = headers 72 | try configuration.requestMiddleware?.forEach { middleware in 73 | newHeaders = try middleware.init().processHeadersAfterReceive(with: newHeaders) 74 | } 75 | return newHeaders 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Source/Extensions/Request+NSCopying.swift: -------------------------------------------------------------------------------- 1 | // Request+NSCopying.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | extension Request: NSCopying { 23 | /// Clones a Request instance. 24 | public func copy(with zone: NSZone? = nil) -> Any { 25 | let request = Request() 26 | request.method = method 27 | request.queue = queue 28 | request.params = params 29 | request.path = path 30 | request.pathType = pathType 31 | request.mockFilePath = mockFilePath 32 | request.multipartBoundary = multipartBoundary 33 | request.multipartFormDataStream = multipartFormDataStream 34 | request.requestType = requestType 35 | request.headers = headers 36 | request.environment = environment 37 | request.associatedObject = associatedObject 38 | request.configuration = configuration.copy() as? Configuration 39 | ?? Configuration.makeDefaultConfiguration() 40 | 41 | return request 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/Extensions/Request+ResponseHeaders.swift: -------------------------------------------------------------------------------- 1 | // Request+ResponseHeaders.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Request extension for response headers. 23 | public extension Request { 24 | /// 25 | /// Reads the response headers from request after its completion. 26 | /// - parameters: 27 | /// - headersCallback: A closure that provides the response headers or an error. 28 | @discardableResult 29 | func responseHeaders(_ headersCallback: @escaping ([String: String]?, TNError?) -> Void) -> Self { 30 | guard processedHeaders == nil else { 31 | headersCallback(processedHeaders, nil) 32 | return self 33 | } 34 | 35 | self.responseHeadersClosure = { [weak self] urlResponse in 36 | guard let headers = (urlResponse as? HTTPURLResponse)?.allHeaderFields as? [String: String], 37 | let processedHeaders = try? self?.handleMiddlewareHeadersAfterReceiveIfNeeded(headers: headers) else { 38 | headersCallback(nil, .cannotReadResponseHeaders) 39 | return 40 | } 41 | self?.processedHeaders = processedHeaders 42 | headersCallback(processedHeaders, nil) 43 | } 44 | return self 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/Extensions/URLRequest+Extensions.swift: -------------------------------------------------------------------------------- 1 | // Request+Extensions.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | extension URLRequest { 23 | /// Returns a cURL command representation of this URL request. 24 | /// Taken from: https://gist.github.com/shaps80/ba6a1e2d477af0383e8f19b87f53661d 25 | internal var curlString: String { 26 | guard let url = url else { return "" } 27 | var baseCommand = "curl \(url.absoluteString)" 28 | 29 | if httpMethod == "HEAD" { 30 | baseCommand += " --head" 31 | } 32 | 33 | var command = [baseCommand] 34 | 35 | if let method = httpMethod, method != "GET" && method != "HEAD" { 36 | command.append("-X \(method)") 37 | } 38 | if let headers = allHTTPHeaderFields { 39 | for (key, value) in headers where key != "Cookie" { 40 | command.append("-H '\(key): \(value)'") 41 | } 42 | } 43 | if let data = httpBody, let body = String(data: data, encoding: .utf8) { 44 | command.append("-d '\(body)'") 45 | } 46 | 47 | return command.joined(separator: " \\\n\t") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/FileStreamer.swift: -------------------------------------------------------------------------------- 1 | // FileStreamer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | typealias ChunkType = (Data?) throws -> Void 23 | 24 | internal class FileStreamer { 25 | var nextChunkClosure: ChunkType? 26 | var bufferSize: Int = 1024 27 | var fileSize: Int = -1 28 | var sizeRead: Int = 0 29 | var bytesRead: Int = 0 30 | var fileHandle: FileHandle? 31 | var url: URL 32 | 33 | lazy var buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) 34 | 35 | init(url: URL, bufferSize: Int) throws { 36 | self.url = url 37 | self.bufferSize = bufferSize 38 | self.fileSize = try MultipartFormDataHelpers.fileSize(withURL: url) 39 | self.fileHandle = FileHandle(forReadingAtPath: url.path) 40 | } 41 | 42 | func readNextChunk(seekToOffset offset: Int = 0, nextChunkClosure: ChunkType? = nil) throws { 43 | if let fileHandle = fileHandle { 44 | try autoreleasepool { 45 | try fileHandle.seek(toOffset: UInt64(offset)) 46 | self.sizeRead = offset + self.bufferSize 47 | let data = fileHandle.readData(ofLength: self.bufferSize) 48 | try nextChunkClosure?(data) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/Helpers/RequestBodyGenerators.swift: -------------------------------------------------------------------------------- 1 | // RequestBodyGenerators.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | class RequestBodyGenerator { 23 | static func generateURLEncodedString(with params: [String: Any?]) throws -> String { 24 | // Create query string from the given params 25 | let queryString = try params.filter({ $0.value != nil }).map { param -> String in 26 | if let value = String(describing: param.value!) 27 | .addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) { 28 | return param.key + "=" + value 29 | } else { 30 | throw TNError.invalidParams 31 | } 32 | }.joined(separator: "&") 33 | 34 | return queryString 35 | } 36 | 37 | static func generateJSONBodyData(with params: [String: Any?]) throws -> Data { 38 | guard let body = try params.toJSONData() else { 39 | throw TNError.invalidParams 40 | } 41 | return body 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/Helpers/RequestHelpers.swift: -------------------------------------------------------------------------------- 1 | // RequestHelpers.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | typealias RequestProcessReturnType = (data: Data?, tnError: TNError?) 23 | 24 | /// Request helpers. 25 | class RequestHelpers { 26 | /// Generates errors from generated by session task completion handler 27 | /// - Parameters: 28 | /// - serverError: the error object from the completion handler 29 | /// - Returns: 30 | /// - data: Data on success after middleware handler 31 | /// - tnError: TNError on any generated error 32 | static func processData(with request: Request, 33 | data: Data? = nil, 34 | urlResponse: URLResponse?, 35 | serverError: Error?) -> RequestProcessReturnType { 36 | var customError: TNError? 37 | var data = data 38 | 39 | /// Error handling 40 | if let error = serverError { 41 | if (error as NSError).code == NSURLErrorCancelled { 42 | if request.pinningErrorOccured { 43 | customError = TNError.pinningError 44 | } else { 45 | customError = TNError.cancelled(error) 46 | } 47 | } else { 48 | customError = TNError.networkError(error) 49 | } 50 | } else if let response = urlResponse as? HTTPURLResponse { 51 | let statusCode = response.statusCode 52 | if statusCode / 100 != 2 { 53 | customError = TNError.notSuccess(statusCode, data ?? .init()) 54 | } 55 | } 56 | 57 | if customError == nil { 58 | do { 59 | data = try request.handleMiddlewareProcessResponseIfNeeded(responseData: data) 60 | } catch { 61 | if let error = error as? TNError { 62 | customError = error 63 | } 64 | } 65 | } 66 | 67 | return RequestProcessReturnType(data: data, tnError: customError) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Source/MultipartFormDataPartType.swift: -------------------------------------------------------------------------------- 1 | // MultipartFormDataPartType.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Enum to specify multipart/form-data parameters that can be used in upload tasks. 23 | public enum MultipartFormDataPartType { 24 | /// Simple key-value case. 25 | /// - Parameters 26 | /// - value: The value of the multipart/form-data parameter. 27 | case value(value: String) 28 | 29 | /// Data case with filename and content-type. 30 | /// - Parameters 31 | /// - data: The data to upload of the multipart/form-data parameter. 32 | /// - filename: The filename value of the multipart/form-data parameter. 33 | /// - contentType: The Content-Type of the multipart/form-data parameter. 34 | case data(data: Data, filename: String?, contentType: String?) 35 | 36 | /// File URL case. 37 | /// - Parameters 38 | /// - url: The file URL that contains the data that will be uploaded. 39 | case url(_ url: URL) 40 | } 41 | -------------------------------------------------------------------------------- /Source/Operation.swift: -------------------------------------------------------------------------------- 1 | // Operation.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | // swiftlint:disable identifier_name 23 | 24 | /// This class is inherited by Request to control the execution of the request. 25 | public class Operation: Foundation.Operation, @unchecked Sendable { 26 | internal var _executing = false { 27 | willSet { 28 | willChangeValue(forKey: "isExecuting") 29 | } 30 | didSet { 31 | didChangeValue(forKey: "isExecuting") 32 | } 33 | } 34 | 35 | /// Overrides the default isExecuting variable inherited from Operation. 36 | override open var isExecuting: Bool { 37 | return _executing 38 | } 39 | 40 | internal var _finished = false { 41 | willSet { 42 | willChangeValue(forKey: "isFinished") 43 | } 44 | 45 | didSet { 46 | didChangeValue(forKey: "isFinished") 47 | } 48 | } 49 | 50 | /// Overrides the default isFinished variable inherited from Operation. 51 | override open var isFinished: Bool { 52 | return _finished 53 | } 54 | 55 | func executing(_ executing: Bool) { 56 | _executing = executing 57 | } 58 | 59 | func finished(_ finished: Bool) { 60 | _finished = finished 61 | } 62 | } 63 | 64 | // swiftlint:enable identifier_name 65 | -------------------------------------------------------------------------------- /Source/Protocols/EndpointProtocol.swift: -------------------------------------------------------------------------------- 1 | // EndpointProtocol.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Use this protocol to define repositories as enums. 23 | public protocol EndpointProtocol { 24 | /// Configure your endpoints by setting defining this function. See 25 | /// Examples/Communication/Repositories/CitiesRepository.swift for an example. 26 | /// - returns: An EndpointProtocol for each endpoint. 27 | func configure() -> EndpointConfiguration 28 | } 29 | -------------------------------------------------------------------------------- /Source/Protocols/InterceptorProtocol.swift: -------------------------------------------------------------------------------- 1 | // InterceptorProtocol.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Use this protocol to create interceptors that can be passed to Configuration instances. 23 | /// Every class which implements this protocol will intercept between request completion and callbacks. 24 | public protocol InterceptorProtocol { 25 | /// This function is called when a request is failed. 26 | /// - parameters: 27 | /// - responseData: The response data of request. 28 | /// - error: The TNError provided by TermiNetwork on request failure, otherwise nil value is passed. 29 | /// - request: The Request object. 30 | func requestFinished(responseData data: Data?, 31 | error: TNError?, 32 | request: Request, 33 | proceed: @escaping (InterceptionAction) -> Void) 34 | 35 | /// Default initializer 36 | init() 37 | } 38 | -------------------------------------------------------------------------------- /Source/Protocols/RequestMiddlewareProtocol.swift: -------------------------------------------------------------------------------- 1 | // RequestMiddlewareProtocol.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// This protocol is used to register a middleware in order to modify body and headers of a request. (e.g. it can be 23 | /// used with Crypto Swift library to encrypt or decrypt the data. 24 | public protocol RequestMiddlewareProtocol { 25 | /// Processes body params before they are sent to server. 26 | /// - parameters: 27 | /// - params: The body params that are constructed by Request initializers. 28 | /// - returns: The new modified params. 29 | func processParams(with params: [String: Any?]?) throws -> [String: Any?]? 30 | 31 | /// Processes response data after they have been received. 32 | /// - parameters: 33 | /// - data: The response data. 34 | /// - returns: The new modified data 35 | func processResponse(with data: Data?) throws -> Data? 36 | 37 | /// Processes the response headers before they are sent to server. 38 | /// - parameters: 39 | /// - headers: the headers cosntructed by initializers and configuration 40 | /// - returns: The new modified headers. 41 | func processHeadersBeforeSend(with headers: [String: String]?) throws -> [String: String]? 42 | 43 | /// Processes response headers after they have been received. 44 | /// - parameters: 45 | /// - headers: the headers cosntructed by initializers and configuration. 46 | /// - returns: The new modified headers. 47 | func processHeadersAfterReceive(with headers: [String: String]?) throws -> [String: String]? 48 | 49 | /// Required initializer 50 | init() 51 | } 52 | -------------------------------------------------------------------------------- /Source/Types.swift: -------------------------------------------------------------------------------- 1 | // Types.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | /// Progress callback type 23 | /// - parameters: 24 | /// - bytesProcessed: The total size of bytes sent/downloaded to/from server. 25 | /// - totalBytes: The total size of upload/download data. 26 | /// - progress: Download/Upload progress 27 | public typealias ProgressCallbackType = (_ bytesProcessed: Int, 28 | _ totalBytes: Int, 29 | _ progress: Float) -> Void 30 | 31 | /// Custom type for success data task. 32 | /// - parameters: 33 | /// - object: The object to be returned as T type. 34 | public typealias SuccessCallback = (_ object: T) -> Void 35 | /// Custom type for success data task without type. 36 | public typealias SuccessCallbackWithoutType = () -> Void 37 | /// Custom type for download success data task. 38 | public typealias DownloadSuccessCallback = () -> Void 39 | /// Custom type for failure data task. 40 | 41 | /// Failure callback (Obsolete). 42 | /// - parameters: 43 | /// - error: The the error that caused the request to fail. 44 | /// - data: The response data if any. 45 | public typealias FailureCallback = (_ error: TNError, _ data: Data?) -> Void 46 | /// Custom type for failure data task without type. 47 | /// - parameters: 48 | /// - error: The the error that caused the request to fail. 49 | public typealias FailureCallbackWithoutType = (TNError) -> Void 50 | /// Custom type for failure data task with custom type. 51 | /// - parameters: 52 | /// - object: The object to be returned as T type. 53 | /// - error: The the error that caused the request to fail. 54 | public typealias FailureCallbackWithType = (_ object: T?, _ error: TNError) -> Void 55 | -------------------------------------------------------------------------------- /TermiNetwork.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TermiNetwork' 3 | s.version = '4.1.2' 4 | s.summary = 'A zero-dependency networking solution for building modern and secure iOS, watchOS, macOS and tvOS applications.' 5 | s.homepage = 'https://github.com/billp/TermiNetwork.git' 6 | s.license = 'MIT' 7 | s.authors = { 'Bill Panagiotopoulos' => 'billp.dev@gmail.com' } 8 | s.source = { :git => 'https://github.com/billp/TermiNetwork.git', :tag => s.version } 9 | s.documentation_url = 'https://billp.github.io/TermiNetwork' 10 | 11 | s.ios.deployment_target = '13.0' 12 | s.osx.deployment_target = '10.15' 13 | s.watchos.deployment_target = '6.0' 14 | s.tvos.deployment_target = '13.0' 15 | 16 | s.source_files = 'Source/**/*.swift' 17 | 18 | s.swift_versions = ['5.3'] 19 | end 20 | -------------------------------------------------------------------------------- /TermiNetwork.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TermiNetwork.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TermiNetwork.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TermiNetwork.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "cryptoswift", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/krzyzanowskim/CryptoSwift", 7 | "state" : { 8 | "revision" : "5669f222e46c8134fb1f399c745fa6882b43532e", 9 | "version" : "1.3.8" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /TermiNetwork.xcodeproj/xcshareddata/xcschemes/TermiNetwork.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /TermiNetwork.xcodeproj/xcshareddata/xcschemes/TermiNetworkExamples (iOS).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /TermiNetwork.xcodeproj/xcshareddata/xcschemes/TermiNetworkTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 17 | 23 | 24 | 25 | 26 | 28 | 34 | 35 | 36 | 37 | 38 | 48 | 49 | 55 | 56 | 58 | 59 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /TermiNetwork/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /TermiNetwork/TermiNetwork.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | com.apple.security.files.user-selected.read-write 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /TermiNetwork/TermiNetwork.h: -------------------------------------------------------------------------------- 1 | // TermiNetwork.h 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | #import 21 | 22 | //! Project version number for TermiNetwork. 23 | FOUNDATION_EXPORT double TermiNetworkVersionNumber; 24 | 25 | //! Project version string for TermiNetwork. 26 | FOUNDATION_EXPORT const unsigned char TermiNetworkVersionString[]; 27 | 28 | // In this header, you should import all the public headers of your framework using statements like #import 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/DoNothingInterceptor.swift: -------------------------------------------------------------------------------- 1 | // GlobalInterceptor.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | // swiftlint:disable compiler_protocol_init 24 | 25 | final class DoNothingInterceptor: InterceptorProtocol { 26 | func requestFinished(responseData data: Data?, 27 | error: TNError?, 28 | request: Request, 29 | proceed: (InterceptionAction) -> Void) { 30 | request.associatedObject = NSNumber(booleanLiteral: true) 31 | proceed(.continue) 32 | } 33 | } 34 | 35 | // swiftlint:enable compiler_protocol_init 36 | -------------------------------------------------------------------------------- /Tests/GlobalInterceptor.swift: -------------------------------------------------------------------------------- 1 | // GlobalInterceptor.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | final class GlobalInterceptor: InterceptorProtocol { 24 | var retryLimit = 5 25 | var delay: TimeInterval = 1.3 26 | 27 | func requestFinished(responseData data: Data?, 28 | error: TNError?, 29 | request: Request, 30 | proceed: (InterceptionAction) -> Void) { 31 | if case .networkError = error, request.retryCount < retryLimit { 32 | if request.retryCount == 4 { 33 | // Set the correct environment in order the request to succeed. 34 | request.environment = Env.termiNetworkRemote.configure() 35 | } 36 | proceed(.retry(delay: delay)) 37 | } else { 38 | proceed(.continue) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/MockData.bundle/main-repo/headers.json: -------------------------------------------------------------------------------- 1 | { 2 | "HTTP_CUSTOM_HEADER" : "yo man!!!!", 3 | "HTTP_ACCEPT" : "*\/*", 4 | "HTTP_USER_AGENT" : "xctest\/15702 CFNetwork\/1121.2.1 Darwin\/19.2.0", 5 | "HTTP_VERSION" : "HTTP\/1.1", 6 | "HTTP_AUTHORIZATION" : "XKJajkBXAUIbakbxjkasbxjkas", 7 | "HTTP_X_FORWARDED_PROTO" : "https", 8 | "HTTP_CONNECT_TIME" : "1", 9 | "HTTP_X_FORWARDED_FOR" : "176.58.225.54", 10 | "HTTP_CONNECTION" : "close", 11 | "HTTP_X_REQUEST_ID" : "c98e4f03-6b36-40a8-bcf2-74891c9f3631", 12 | "HTTP_X_REQUEST_START" : "1586719366619", 13 | "HTTP_X_FORWARDED_PORT" : "443", 14 | "HTTP_TOTAL_ROUTE_TIME" : "0", 15 | "HTTP_ACCEPT_ENCODING" : "gzip, deflate, br", 16 | "HTTP_ACCEPT_LANGUAGE" : "en-us", 17 | "HTTP_VIA" : "1.1 vegur", 18 | "HTTP_HOST" : "terminetwork-rails-app.herokuapp.com" 19 | } 20 | -------------------------------------------------------------------------------- /Tests/Models/Domain/TestModel.swift: -------------------------------------------------------------------------------- 1 | // TestModel.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct TestModel { 23 | var value: String 24 | var param: String? 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Models/Rest/EncryptedModel.swift: -------------------------------------------------------------------------------- 1 | // EncryptedModel.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | class EncryptedModel: Codable { 23 | let value: String 24 | } 25 | -------------------------------------------------------------------------------- /Tests/Models/Rest/ErrorModel.swift: -------------------------------------------------------------------------------- 1 | // ErrorModel.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | class ErrorModel: Codable { 23 | var errorMessage: String 24 | } 25 | -------------------------------------------------------------------------------- /Tests/Models/Rest/FileResponse.swift: -------------------------------------------------------------------------------- 1 | // FileResponse.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | class FileResponse: Codable { 23 | var success: Bool 24 | var checksum: String 25 | var param: String 26 | } 27 | -------------------------------------------------------------------------------- /Tests/Models/Rest/StatusCode.swift: -------------------------------------------------------------------------------- 1 | // StatusCode.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct StatusCode: Decodable { 23 | var statusCode: String 24 | } 25 | -------------------------------------------------------------------------------- /Tests/Models/Rest/TestHeaders.swift: -------------------------------------------------------------------------------- 1 | // Queue.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct TestHeaders: Codable { 23 | let authorization: String? 24 | let customHeader: String? 25 | let userAgent: String? 26 | 27 | enum CodingKeys: String, CodingKey { 28 | case authorization = "HTTP_AUTHORIZATION" 29 | case customHeader = "HTTP_CUSTOM_HEADER" 30 | case userAgent = "HTTP_USER_AGENT" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/Models/Rest/TestJSONParams.swift: -------------------------------------------------------------------------------- 1 | // Queue.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct TestJSONParams: Codable { 23 | 24 | let param1: Bool 25 | let param2: Int 26 | let param3: Double 27 | let param4: String 28 | let param5: String? 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case param1 = "key1" 32 | case param2 = "key2" 33 | case param3 = "key3" 34 | case param4 = "key4" 35 | case param5 = "key5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Models/Rest/TestParams.swift: -------------------------------------------------------------------------------- 1 | // Queue.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | 22 | struct TestParams: Codable { 23 | 24 | let param1: String 25 | let param2: String 26 | let param3: String 27 | let param4: String 28 | let param5: String? 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case param1 = "key1" 32 | case param2 = "key2" 33 | case param3 = "key3" 34 | case param4 = "key4" 35 | case param5 = "key5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/StatusCodeTransformer.swift: -------------------------------------------------------------------------------- 1 | // TestTransformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | class StatusCodeTransformer: Transformer { 24 | override func transform(_ object: StatusCode) -> TestModel { 25 | let model = TestModel(value: object.statusCode, 26 | param: nil) 27 | 28 | return model 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/TestCache.swift: -------------------------------------------------------------------------------- 1 | // TestCache.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import XCTest 21 | @testable import TermiNetwork 22 | 23 | class TestCache: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | } 28 | 29 | override func tearDown() { 30 | super.tearDown() 31 | } 32 | 33 | func testConfigureCache() { 34 | let count = 200 35 | let size = 1024*100*100 36 | 37 | Cache.shared.configureCache(countLimit: count, size: size) 38 | 39 | XCTAssert(Cache.shared.cache.countLimit == count) 40 | XCTAssert(Cache.shared.cache.totalCostLimit == size) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/TestEnvironment.swift: -------------------------------------------------------------------------------- 1 | // TestEnvironment.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import XCTest 21 | import TermiNetwork 22 | 23 | class TestEnvironment: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | func testEnvironments() { 36 | Environment.set(Env.httpHost) 37 | XCTAssert(Environment.current.stringURL == "http://localhost") 38 | 39 | Environment.set(Env.httpHostWithPort) 40 | XCTAssert(Environment.current.stringURL == "http://localhost:8080") 41 | 42 | Environment.set(environmentObject: Environment(url: "http://www.google.com:8009/test/2")) 43 | XCTAssert(Environment.current.stringURL == "http://www.google.com:8009/test/2") 44 | 45 | Environment.set(Env.httpHostWithPortAndSuffix) 46 | XCTAssert(Environment.current.stringURL == "http://localhost:8080/v1/json") 47 | 48 | Environment.set(Env.httpsHostWithPortAndSuffix) 49 | XCTAssert(Environment.current.stringURL == "https://google.com:8080/v3/test/foo/bar") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/TestPinning.swift: -------------------------------------------------------------------------------- 1 | // TestPinning.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import XCTest 21 | import TermiNetwork 22 | 23 | class TestPinning: XCTestCase { 24 | 25 | var bundle: Bundle = { 26 | return Bundle(for: TestPinning.self) 27 | }() 28 | 29 | var invalidCertPath: String { 30 | return bundle.path(forResource: "forums.swift.org", 31 | ofType: "cer", 32 | inDirectory: nil, 33 | forLocalization: nil) ?? "" 34 | } 35 | 36 | var validCertPath: String { 37 | return bundle.path(forResource: "terminetwork.billp.dev", 38 | ofType: "cer", 39 | inDirectory: nil, 40 | forLocalization: nil) ?? "" 41 | } 42 | 43 | override func setUp() { 44 | super.setUp() 45 | // Put setup code here. This method is called before the invocation of each test method in the class. 46 | Environment.set(Env.termiNetworkRemote) 47 | } 48 | 49 | override func tearDown() { 50 | // Put teardown code here. This method is called after the invocation of each test method in the class. 51 | super.tearDown() 52 | } 53 | 54 | func testValidCertificate() { 55 | let expectation = XCTestExpectation(description: "testValidCertificate") 56 | var failed = true 57 | 58 | Request(endpoint: TestRepository.testPinning(certPath: validCertPath)) 59 | .success(responseType: String.self) { _ in 60 | failed = false 61 | expectation.fulfill() 62 | } 63 | .failure { _ in 64 | failed = true 65 | expectation.fulfill() 66 | } 67 | 68 | wait(for: [expectation], timeout: 100) 69 | 70 | XCTAssert(!failed) 71 | } 72 | 73 | func testInvalidCertificate() { 74 | let expectation = XCTestExpectation(description: "testInvalidCertificate") 75 | var failed = true 76 | 77 | Request(endpoint: TestRepository.testPinning(certPath: invalidCertPath)) 78 | .success(responseType: String.self) { _ in 79 | failed = true 80 | expectation.fulfill() 81 | } 82 | .failure { error in 83 | if case .pinningError = error { 84 | failed = false 85 | } else { 86 | failed = true 87 | } 88 | expectation.fulfill() 89 | } 90 | 91 | wait(for: [expectation], timeout: 100) 92 | 93 | XCTAssert(!failed) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Tests/TestTransformer.swift: -------------------------------------------------------------------------------- 1 | // TestTransformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | class TestTransformer: Transformer { 24 | override func transform(_ object: TestParams) -> TestModel { 25 | let model = TestModel(value: object.param1, 26 | param: nil) 27 | 28 | return model 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/TestUploadTransformer.swift: -------------------------------------------------------------------------------- 1 | // TestUploadTransformer.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | class TestUploadTransformer: Transformer { 24 | override func transform(_ object: FileResponse) -> TestModel { 25 | let testModel = TestModel(value: object.checksum, 26 | param: object.param) 27 | return testModel 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/UnauthorizedInterceptor.swift: -------------------------------------------------------------------------------- 1 | // GlobalInterceptor.swift 2 | // 3 | // Copyright © 2018-2023 Vassilis Panagiotopoulos. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in the 7 | // Software without restriction, including without limitation the rights to use, copy, 8 | // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 9 | // and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all copies 12 | // or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIESS FOR A PARTICULAR 16 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | import Foundation 21 | import TermiNetwork 22 | 23 | final class UnauthorizedInterceptor: InterceptorProtocol { 24 | let retryDelay: TimeInterval = 0.1 25 | let retryLimit = 5 26 | 27 | static var currentStatusCode = 401 28 | static let authorizationValue = "abcdef123" 29 | 30 | func requestFinished(responseData data: Data?, 31 | error: TNError?, 32 | request: Request, 33 | proceed: @escaping (InterceptionAction) -> Void) { 34 | switch error { 35 | case .notSuccess(let statusCode, _): 36 | if statusCode == 401, request.retryCount < retryLimit { 37 | // Login to get a new token. 38 | UnauthorizedInterceptor.currentStatusCode = 200 39 | 40 | // Update global header in configuration which is inherited by all requests. 41 | Environment.current.configuration?.headers?["Authorization"] = 42 | UnauthorizedInterceptor.authorizationValue 43 | 44 | // Update current request's header. 45 | request.headers?["Authorization"] = UnauthorizedInterceptor.authorizationValue 46 | request.params?["status_code"] = UnauthorizedInterceptor.currentStatusCode 47 | // Retry the original request. 48 | proceed(.retry(delay: retryDelay)) 49 | } 50 | default: 51 | proceed(.continue) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/forums.swift.org.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Tests/forums.swift.org.cer -------------------------------------------------------------------------------- /Tests/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Tests/photo.jpg -------------------------------------------------------------------------------- /Tests/terminetwork.billp.dev.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Tests/terminetwork.billp.dev.cer -------------------------------------------------------------------------------- /Tests/www.billp.dev.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/Tests/www.billp.dev.cer -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 98% 23 | 24 | 25 | 98% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy. 7 | CFBundleName 8 | 9 | DocSetPlatformFamily 10 | 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 23% 23 | 24 | 25 | 23% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /docs/docsets/.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/.tgz -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.terminetwork 7 | CFBundleName 8 | TermiNetwork 9 | DocSetPlatformFamily 10 | terminetwork 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | DashDocSetFallbackURL 20 | https://billp.github.io/TermiNetwork/ 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 98% 23 | 24 | 25 | 98% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targetted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/Documents/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | { 4 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 5 | "line": 22, 6 | "symbol": "TransformerProtocol", 7 | "symbol_kind": "source.lang.swift.decl.protocol", 8 | "warning": "undocumented" 9 | }, 10 | { 11 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 12 | "line": 23, 13 | "symbol": "TransformerProtocol.FromType", 14 | "symbol_kind": "source.lang.swift.decl.associatedtype", 15 | "warning": "undocumented" 16 | }, 17 | { 18 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 19 | "line": 24, 20 | "symbol": "TransformerProtocol.ToType", 21 | "symbol_kind": "source.lang.swift.decl.associatedtype", 22 | "warning": "undocumented" 23 | }, 24 | { 25 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 26 | "line": 26, 27 | "symbol": "TransformerProtocol.transform(_:)", 28 | "symbol_kind": "source.lang.swift.decl.function.method.instance", 29 | "warning": "undocumented" 30 | } 31 | ], 32 | "source_directory": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork" 33 | } -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/TermiNetwork.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/docsets/TermiNetwork.tgz -------------------------------------------------------------------------------- /docs/docsets/TermiNetwork.xml: -------------------------------------------------------------------------------- 1 | 3.2.0https://billp.github.io/TermiNetwork/docsets/TermiNetwork.tgz 2 | -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billp/TermiNetwork/e142d379de59f31d47f391021e79d76a852acce6/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targetted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /docs/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | { 4 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 5 | "line": 22, 6 | "symbol": "TransformerProtocol", 7 | "symbol_kind": "source.lang.swift.decl.protocol", 8 | "warning": "undocumented" 9 | }, 10 | { 11 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 12 | "line": 23, 13 | "symbol": "TransformerProtocol.FromType", 14 | "symbol_kind": "source.lang.swift.decl.associatedtype", 15 | "warning": "undocumented" 16 | }, 17 | { 18 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 19 | "line": 24, 20 | "symbol": "TransformerProtocol.ToType", 21 | "symbol_kind": "source.lang.swift.decl.associatedtype", 22 | "warning": "undocumented" 23 | }, 24 | { 25 | "file": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork/Source/Extensions/Operations/Decodable+Transformer.swift", 26 | "line": 26, 27 | "symbol": "TransformerProtocol.transform(_:)", 28 | "symbol_kind": "source.lang.swift.decl.function.method.instance", 29 | "warning": "undocumented" 30 | } 31 | ], 32 | "source_directory": "/Users/v.a.panagiotopoulos/Projects/TermiNetwork" 33 | } --------------------------------------------------------------------------------