{"id":20833856,"url":"https://github.com/denissimon/urlsessionadapter","last_synced_at":"2026-01-05T21:14:36.238Z","repository":{"id":218698992,"uuid":"747144748","full_name":"denissimon/URLSessionAdapter","owner":"denissimon","description":"A Codable wrapper around URLSession for networking.","archived":false,"fork":false,"pushed_at":"2024-05-22T22:09:41.000Z","size":104,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-05-22T22:37:04.552Z","etag":null,"topics":["adapter","api-adapter","api-client","async-await","carthage","cocoapods","codable","http-client","ios","macos","network-service","networking","rest-api","swift","swift-library","swift-package-manager","urlrequest","urlsession","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/denissimon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-23T11:01:23.000Z","updated_at":"2024-05-29T22:50:46.960Z","dependencies_parsed_at":"2024-02-07T21:27:30.892Z","dependency_job_id":"57b5eee3-f90f-4498-9814-a52181a340d6","html_url":"https://github.com/denissimon/URLSessionAdapter","commit_stats":{"total_commits":15,"total_committers":1,"mean_commits":15.0,"dds":0.0,"last_synced_commit":"080cf4de6d0dbec40358ae02c083b1a2ebb294cf"},"previous_names":["denissimon/urlsessionadapter"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denissimon%2FURLSessionAdapter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denissimon%2FURLSessionAdapter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denissimon%2FURLSessionAdapter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denissimon%2FURLSessionAdapter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/denissimon","download_url":"https://codeload.github.com/denissimon/URLSessionAdapter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245111928,"owners_count":20562511,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adapter","api-adapter","api-client","async-await","carthage","cocoapods","codable","http-client","ios","macos","network-service","networking","rest-api","swift","swift-library","swift-package-manager","urlrequest","urlsession","xcode"],"created_at":"2024-11-18T00:17:03.632Z","updated_at":"2026-01-05T21:14:36.221Z","avatar_url":"https://github.com/denissimon.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# URLSessionAdapter\n\n[![Swift](https://img.shields.io/badge/Swift-6-orange.svg?style=flat)](https://swift.org)\n[![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Linux-lightgrey.svg)](https://developer.apple.com/swift/)\n\nA Codable wrapper around URLSession for networking. Includes both APIs: async/await and callbacks.\n\nSupports:\n\n* `Data`, `Upload`, and `Download` URL session tasks\n* HTTP methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, TRACE, QUERY\n* Automatic validation: global or per request based on the received status code\n* Delegates to receive progress updates\n\nInstallation\n------------\n\n#### Swift Package Manager\n\nTo install URLSessionAdapter using [Swift Package Manager](https://swift.org/package-manager), add the following in your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/denissimon/URLSessionAdapter.git\", from: \"2.2.10\")\n]\n```\n\nOr through Xcode:\n\n```txt\nFile -\u003e Add Package Dependencies\nEnter Package URL: https://github.com/denissimon/URLSessionAdapter\n```\n\n#### CocoaPods\n\nTo install URLSessionAdapter using [CocoaPods](https://cocoapods.org), add this line to your `Podfile`:\n\n```ruby\npod 'URLSessionAdapter', '~\u003e 2.2'\n```\n\n#### Carthage\n\nTo install URLSessionAdapter using [Carthage](https://github.com/Carthage/Carthage), add this line to your `Cartfile`:\n\n```ruby\ngithub \"denissimon/URLSessionAdapter\"\n```\n\n#### Manually\n\nCopy folder `URLSessionAdapter` into your project.\n\nUsage\n-----\n\n### Define a Decodable/Codable instance\n\n```swift\nstruct Activity: Decodable {\n    let id: Int?\n    let name: String\n    let description: String\n}\n```\n\n### Define API endpoints\n\n```swift\nimport URLSessionAdapter\n\nstruct APIEndpoints {\n    \n    static let baseURL = \"https://api.example.com/rest\"\n    \n    static func getActivity(id: Int) -\u003e EndpointType {\n        let path = \"/activities/\\(id)/?key=\\(Secrets.apiKey)\"        \n        return Endpoint(\n            method: .GET,\n            baseURL: APIEndpoints.baseURL,\n            path: path,\n            params: nil)\n    }\n    \n    static func createActivity(_ activity: Activity) -\u003e EndpointType {\n        let path = \"/activities/?key=\\(Secrets.apiKey)\"\n\n        let params = HTTPParams(httpBody: activity.encode(), headerValues: [(\n            value: \"application/json\",\n            forHTTPHeaderField: \"Content-Type\")\n        ])\n        \n        return Endpoint(\n            method: .POST,\n            baseURL: APIEndpoints.baseURL,\n            path: path,\n            params: params)\n    }\n}\n```\n\n### Define API methods\n\n```swift\nimport URLSessionAdapter\n\nclass ActivityRepository {\n    \n    let networkService: NetworkService\n    \n    init(networkService: NetworkService) {\n        self.networkService = networkService\n    }\n    \n    func getActivity(id: Int) async -\u003e Result\u003cActivity, CustomError\u003e {\n        let endpoint = APIEndpoints.getActivity(id: id)\n        guard let request = RequestFactory.request(endpoint) else { ... }\n        do {\n            let (activity, _) = try await networkService.request(request, type: Activity.self)\n            return .success(activity)\n        } catch {\n            ...\n        }\n    }\n    \n    func createActivity(_ activity: Activity) async -\u003e Result\u003cData, CustomError\u003e {\n        let endpoint = APIEndpoints.createActivity(activity)\n        guard let request = RequestFactory.request(endpoint) else { ... }\n        do {\n            let (data, _) = try await networkService.request(request)\n            return .success(data)\n        } catch {\n            ...\n        }\n    }\n}\n```\n\n### API calls\n\n```swift\nlet networkService = NetworkService(urlSession: URLSession.shared)\nlet activityRepository = ActivityRepository(networkService: networkService)\n\nTask {\n    let result = await activityRepository.getActivity(id: 1)\n    switch result {\n    case .success(let activity):\n        ...\n    case .failure(let error):\n        ...\n    }\n}\n\nTask {\n    let result = await activityRepository.createActivity(activity) // will return the id of the created activity\n    switch result {\n    case .success(let data):\n        guard let data = data, \n              let activityId = Int(String(data: data, encoding: .utf8) ?? \"\") else { ... }\n        ...\n    case .failure(let error):\n        ...\n    }\n}\n```\n\nFetch a file:\n\n```swift\nlet data = try await networkService.fetchFile(url).data\nguard let image = UIImage(data: data) else {\n    ...\n}\n```\n\nDownload a file:\n\n```swift\nguard try await networkService.downloadFile(url, to: localUrl).result else {\n    ...\n}\n```\n\nUpload a file:\n\n```swift\nlet endpoint = SomeAPI.uploadFile(file)\nguard let request = RequestFactory.request(endpoint) else { return }\nlet config = RequestConfiguration(uploadTask: true)\nlet (data, response) = try await networkService.request(request, configuration: config)\n```\n\nCheck the returned status code:\n\n```swift\nguard let httpResponse = response as? HTTPURLResponse else { return }\nassert(httpResponse.statusCode == 200)\n```\n\n### Validation\n\nBy default, any 300-599 status code returned by the server throws a `NetworkError`:\n\n```swift\ndo {\n    let response = try await networkService.request(request) // will return status code 404\n    ...\n} catch let networkError as NetworkError {\n    let description = networkError.error?.localizedDescription\n    let statusCode = networkError.statusCode // 404\n    let dataStr = String(data: networkError.data ?? Data(), encoding: .utf8)!\n} catch {\n    // Handling other errors\n}\n```\n\nOptionally, this automatic validation can be disabled globally:\n\n```swift\nnetworkService.autoValidation = false\n\ndo {\n    let response = try await networkService.request(request) // will return status code 404\n    let statusCode = (response as? HTTPURLResponse)?.statusCode // 404\n    let dataStr = String(data: response.data ?? Data(), encoding: .utf8)!\n} catch {\n    ...\n}\n```\n\nOr it can be disabled for a specific request:\n\n```swift\ndo {\n    let config = RequestConfiguration(validation: false)\n    let response = try await networkService.request(request, configuration: config) // will return status code 404\n    let statusCode = (response as? HTTPURLResponse)?.statusCode // 404\n    let dataStr = String(data: response.data ?? Data(), encoding: .utf8)!\n} catch {\n    ...\n}\n```\n\n### Progress updates\n\n```swift\nlet progressObserver = ProgressObserver {\n    print($0.fractionCompleted) // Example output: 0.05 0.0595 1.0\n}\n    \ndo {\n    let (result, response) = try await networkService.downloadFile(\n        url,\n        to: destinationUrl,\n        delegate: progressObserver\n    )\n    ...\n} catch {\n    ...\n}\n```\n\nMore usage examples can be found in [tests](https://github.com/denissimon/URLSessionAdapter/tree/main/Tests/URLSessionAdapterTests) and [iOS-MVVM-Clean-Architecture](https://github.com/denissimon/iOS-MVVM-Clean-Architecture) where this adapter was used.\n\nPublic methods\n--------------\n\n```swift\n// async/await API\n\nfunc request(_ request: URLRequest, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -\u003e (data: Data, response: URLResponse)\nfunc request\u003cT: Decodable\u003e(_ request: URLRequest, type: T.Type, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -\u003e (decoded: T, response: URLResponse)\nfunc fetchFile(_ url: URL, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -\u003e (data: Data?, response: URLResponse)\nfunc downloadFile(_ url: URL, to localUrl: URL, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -\u003e (result: Bool, response: URLResponse)\n\n// callbacks API\n\nfunc request(_ request: URLRequest, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result\u003c(data: Data?, response: URLResponse?), NetworkError\u003e) -\u003e Void) -\u003e NetworkCancellable?\nfunc request\u003cT: Decodable\u003e(_ request: URLRequest, type: T.Type, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result\u003c(decoded: T, response: URLResponse?), NetworkError\u003e) -\u003e Void) -\u003e NetworkCancellable?\nfunc fetchFile(_ url: URL, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result\u003c(data: Data?, response: URLResponse?), NetworkError\u003e) -\u003e Void) -\u003e NetworkCancellable?\nfunc downloadFile(_ url: URL, to localUrl: URL, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result\u003c(result: Bool, response: URLResponse?), NetworkError\u003e) -\u003e Void) -\u003e NetworkCancellable?\n```\n\nLicense\n-------\n\nLicensed under the [MIT license](https://github.com/denissimon/URLSessionAdapter/blob/main/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdenissimon%2Furlsessionadapter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdenissimon%2Furlsessionadapter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdenissimon%2Furlsessionadapter/lists"}