{"id":20565257,"url":"https://github.com/juanjoarreola/osprey","last_synced_at":"2026-04-30T02:37:02.302Z","repository":{"id":101965935,"uuid":"258870212","full_name":"JuanjoArreola/Osprey","owner":"JuanjoArreola","description":"A Library to connect to APIs built on top of URLSession","archived":false,"fork":false,"pushed_at":"2023-02-01T22:39:01.000Z","size":61,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-14T14:07:24.051Z","etag":null,"topics":["api-client","json","json-api","multipart","networking","promise","request","swift","urlsession"],"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/JuanjoArreola.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":"2020-04-25T20:42:50.000Z","updated_at":"2021-11-19T16:55:04.000Z","dependencies_parsed_at":"2023-07-16T18:47:21.860Z","dependency_job_id":null,"html_url":"https://github.com/JuanjoArreola/Osprey","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/JuanjoArreola/Osprey","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuanjoArreola%2FOsprey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuanjoArreola%2FOsprey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuanjoArreola%2FOsprey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuanjoArreola%2FOsprey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JuanjoArreola","download_url":"https://codeload.github.com/JuanjoArreola/Osprey/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JuanjoArreola%2FOsprey/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32452741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"online","status_checked_at":"2026-04-30T02:00:05.929Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["api-client","json","json-api","multipart","networking","promise","request","swift","urlsession"],"created_at":"2024-11-16T04:33:34.149Z","updated_at":"2026-04-30T02:37:02.287Z","avatar_url":"https://github.com/JuanjoArreola.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Osprey\n\nNetworking for Swift 5.\n\n[![GitHub license](https://img.shields.io/github/license/JuanjoArreola/Osprey)](https://github.com/JuanjoArreola/Osprey/blob/master/LICENSE)\n[![codebeat badge](https://codebeat.co/badges/a1b06f95-e950-402f-a0fc-0235813e15c5)](https://codebeat.co/projects/github-com-juanjoarreola-osprey-master)\n\nA Library to connect to APIs built on top of URLSession.\n\n#### Features:\n- Lightweight\n- Not *format specific*\n\n#### Considerations:\n- It depends on a Promises Library\n- Might not be the best option if download or upload tasks are necessary\n\n#### Installation\n- Swift Package Manager\n\n### Quickstart\n\nThis quick example demonstrates how to get a Github user's repositories:\nMapping the API Model:\n\n```swift\nstruct Repository: Decodable {\n    var name: String\n}\n```\n\nCreating an *API*:\n\n```swift\nclass RepositoriesAPI: AbstractAPI {\n    func repositoriesOf(_ username: String) -\u003e Promise\u003c[Repository]\u003e {\n        return request(route: .get(\"https://api.github.com/users/\\(username)/repos\"))\n    }\n}\n```\n\nConsuming the API:\n\n```swift\nlet api = RepositoriesAPI(responseParser: JSONParser())\n\napi.repositoriesOf(\"username\").onSuccess { repositories in\n    print(repositories)\n}.onError { error in\n    print(error)\n}\n```\n\n### Overview\n\nThe goal of Osprey is to be able to write API Clients in a clear and simple way having the \nflexibility to customize the encoding of parameters and the parsing of the responses to \naccommodate many of the common styles in which APIs are structured. \n\nThe first step to make a request with Osprey is to define all the information that will be sent,\nthis information is divided in two parts:\n\n- **Route**, conformed of Method and URL.\n- **Request parameters**, can contain URL parameters, body data and headers.  \n\nThis is to separate the *simple* information always required to make a request (Method and URL)\nfrom the optional information that can take many forms or require additional processing\n(URL query params, body and headers).\n\nWhen the networking client calls the `request` method with the **Route** and **Request parameters**\na **Promise** instance is returned immediately and all the processing necessary to make the actual http\nrequest (setting URL query parameters, encoding body data , setting headers, etc.) is made by a \nbackground queue.\n\nWhen the response is received in a background queue, the data is parsed and the \nresult (success or error) is made available by fulfilling the returned **Promise**\n\n### Making Requests\n\n#### Routes\n\nThe first parameter to make a request is the  `Route`:\n\n```swift\nfunc user() -\u003e Promise\u003cUser\u003e {\n    return request(route: Route.get(\"https://api/users\"))\n}\n```\nMany of the common HTTP Methods are available `GET` `POST` `PUT` `DELETE` `HEAD` `PATCH` `OPTIONS`:\n\n```swift\nRoute.get(\"https://api/users\")\nRoute.post(\"https://api/users\")\nRoute.patch(\"https://api/users\")\nRoute.delete(\"https://api/users\")\n// ...\n\n```\n\nIf the API has a base URL, the API Client can conform to the `BaseAPI` protocol to simplify the routes:\n\n```swift\nclass ProductsAPI: AbstractAPI, BaseAPI {\n    var baseURL = \"https://myapi\"\n    \n    func requestProducts() -\u003e Promise\u003c[Product]\u003e {\n        return request(route: get(endpoint: \"/products\"))\n    }\n}\n```\n\n#### URL Parameters\n\nTo send URL query parameters, call the request method with the `parameters` parameter:\n\n```swift\nfunc requestProducts() -\u003e Promise\u003c[Product]\u003e {\n    let params = URLParameters([\"page\": 1])\n    return request(route: get(endpoint: \"/products\"), parameters: params)\n}\n```\n\n#### Encoders\n\nOsprey has a few parameter encoders included: JSON, Multipart, FormUrl, but is not limited \nto those, you can add your own custom encoder by conforming to the `RequestParameters` protocol\n\n##### JSON\n\nTo encode your parameters as JSON use the `JSONParameters` class:\n\n```swift\nfunc addProduct(_ product: Product) -\u003e Promise\u003cProduct\u003e {\n    let params = JSONParameters(product)\n    return request(route: post(endpoint: \"/products/\"), parameters: params)\n}\n```\n\n##### Multipart\n\n```swift\nfunc addPicture(_ data: Data, to product: Product) -\u003e Promise\u003cPicture\u003e {\n    let part = Part(mimeType: .png, data: data, attributes: [\"name\": \"picture\", \"filename\": UUID().uuidString])\n    let params = MultipartParameters(parts: [part])\n    return request(route: patch(endpoint: \"/products/\\(product.id)/\"), parameters: params)\n}\n```\n\n### Headers\n\nHeaders can be added to every `RequestParameters` instance:\n\n```swift\nlet params = JSONParameters(product, headers: [\"Authentication\": \"Token \\(token)\"])\nlet urlParams = URLParameters(headers: [\"Authentication\": \"Token \\(token)\"])\nlet multipartParams = MultipartParameters(product, headers: [\"Authentication\": \"Token \\(token)\"])\n```\n\nIt could also be convenient to create your own parameters type to handle some cases like Authentication:\n\n```swift\nclass AuthenticatedJSONParameters: JSONParameters {\n    func preprocess() throws {\n        headers[\"Authentication\"] = try UserManager.shared.getToken()\n    }\n}\n\nlet params = AuthenticatedJSONParameters(product)\n```\n\n### Getting Responses\n\nAPI Clients inherit from the `AbstractAPI` class that provides the `request` method,\nthis method returns a `Promise` instance that can be used to register closures to be \ncalled when the response is ready or to chain more requests:\n\n```swift\nusersAPI.requestUser()\n.then(api.getFavorites(of:))\n.onSuccess(updateProducts)\n.onError(logError)\n.finally(updateInterface)\n```\nAll this closures are called in the main queue, to change that behaviour you can configure your \nclient setting the `responseQueue` variable:\n\n```swift\nlet api = UsersAPI(responseParser: JSONParser())\napi.responseQueue = .global()\n```\n\nYou can also set the queue in which a single closure will be called:\n\n```swift\napi.requestUser()\n.then(in: backgroundQueue, api.getFavorites(of:))\n.onSuccess(in: backgroundQueue, cacheProducts)\n.onSuccess(in: .main, updateProducts)\n.onError(in: backgroundQueue, reportError(_:))\n.onError(in: .main, alertError(_:))\n.finally(in: .main, updateInterface)\n```\n\n#### Parsers\n\nAPI Clients need a response parser to deserialize data into your model instances,\nOsprey includes a `JSONParser` that handles json data, you can define your own parsers by\nconfirming to the `ResponseParser` protocol.\n\nThe `JSONParser` expects models as the root of the json data, to change this behaviour you\ncan subclass the `JSONParser` to adapt to the format.\n\nThe following example demonstrates how to parse json data that contains metadata:\n\n```json\n{\n    \"page\": {\n        \"number\": 1,\n        \"size\": 50\n    },\n    \"success\": true,\n    \"results\": [\n        {\n            \"id\": 1,\n            \"name\": \"Product 1\"\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Product 2\"\n        }\n    ]\n}\n```\n\n```swift\nstruct Page: Decodable {\n    var number: Int\n    var size: Int\n}\n\nstruct Response\u003cT: Decodable\u003e: Decodable {\n    var page: Page\n    var results: T\n}\n\nclass CustomJSONParser: JSONParser {\n    override func getInstance\u003cT\u003e(from data: Data, response: URLResponse?) throws -\u003e T where T : Decodable {\n        let response = try decoder.decode(Response\u003cT\u003e.self, from: data)\n        return response.results\n    }\n}\n\nlet productsAPI = ProductsAPI(responseParser: CustomJSONParser())\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanjoarreola%2Fosprey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuanjoarreola%2Fosprey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanjoarreola%2Fosprey/lists"}