{"id":15038604,"url":"https://github.com/Cozmonat/Forest","last_synced_at":"2025-10-04T08:31:39.649Z","repository":{"id":56906508,"uuid":"151091237","full_name":"Cozmonat/Forest","owner":"Cozmonat","description":"Declarative REST API request construction for iOS and macOS","archived":true,"fork":false,"pushed_at":"2020-05-02T12:32:23.000Z","size":325,"stargazers_count":9,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-12-10T10:38:26.785Z","etag":null,"topics":["api-client","client-server","client-side","ios","mobile-development","networking","protobuf","scalable-networking","swift","urlrequest","urlsession","urlsessiontask","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Cozmonat.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}},"created_at":"2018-10-01T13:14:31.000Z","updated_at":"2022-11-16T06:10:10.000Z","dependencies_parsed_at":"2022-08-21T03:50:11.203Z","dependency_job_id":null,"html_url":"https://github.com/Cozmonat/Forest","commit_stats":null,"previous_names":["kzlekk/forest","kozlek/condulet","cozmonat/forest"],"tags_count":66,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cozmonat%2FForest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cozmonat%2FForest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cozmonat%2FForest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cozmonat%2FForest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Cozmonat","download_url":"https://codeload.github.com/Cozmonat/Forest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235232708,"owners_count":18957057,"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":["api-client","client-server","client-side","ios","mobile-development","networking","protobuf","scalable-networking","swift","urlrequest","urlsession","urlsessiontask","xcode"],"created_at":"2024-09-24T20:39:10.379Z","updated_at":"2025-10-04T08:31:39.153Z","avatar_url":"https://github.com/Cozmonat.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://raw.githubusercontent.com/kzlekk/Forest/master/logo.png\"\u003e\n\u003c/p\u003e\n\n# Forest Client\n\n[![License](https://img.shields.io/badge/license-MIT-ff69b4.svg)](https://github.com/kzlekk/Forest/raw/master/LICENSE)\n[![Language](https://img.shields.io/badge/swift-5.0-orange.svg)](https://swift.org/blog/swift-5-released/)\n[![Version](https://img.shields.io/cocoapods/v/Forest.svg)](https://cocoapods.org/pods/Forest)\n[![Build Status](https://travis-ci.com/kzlekk/Forest.svg?branch=master)](https://travis-ci.com/kzlekk/Forest)\n[![Coverage Status](https://coveralls.io/repos/github/kzlekk/Forest/badge.svg?branch=master)](https://coveralls.io/github/kzlekk/Forest?branch=master)\n\nForest client is a flexible and extensible RESTful API client framework built on top of `URLSession` and `URLSessionTask`. It already includes network object mappers from JSON to the most commonly used data types. Because of its simple data encoding/decoding approach and extensible architecture you can easily add your custom network object mappers. Forest provides all of the features needed to build robust client for your backend services. \n\nYou could ask why not to get any other proven networking framework? Sure, you’ll get one best suitable for your needs and style preference, but it's always good to have an options. Following is the list of features I wanted from higher level networking layer and implemented in Forest client:\n\n* Declarative request and response configuration\n* Transparent and completely customizable response interception and rewind/retry, allowing async handling. Mainly to map errors and catch expired tokens in case of OAuth-like secured request \n* Extensibility. Easily add custom response mappers and body data encoders using closures, or protocols, or subclassing whichever will be more convenient for specific need. Use this with default framework’s classes or extend them or subclass, but it should be easy. \n* Download and keep files where I need them\n* Deserialization of JSON body. Map JSON to array, or dictionary, or by using objects conforming to `Decodable` protocol \n* Serialization and deserialization of Protobufs messages (gRPC over HTTP)\n* Multipart form data support as a bonus \n\n## Installation\n\n### CocoaPods\n\n```ruby\npod 'Forest'\n```\n\n\nAdd Protobufs supporting extensions:\n\n```ruby\npod 'Forest/Protobuf'\n```\n\nTo use reachability service:\n\n```ruby\npod 'Forest/Reachability'\n```\n\nAnd don't forget to import the framework:\n\n```swift\nimport Forest\n```\n\n### Manually\n\n\nJust put the files from `Core` and `Protobuf` directories somethere in your project. To use Protobuf extensions you need additionally integrate SwiftProtobuf framework into your project.\n\n\n## Usage\n\n\nThe core class which handles network task is `ServiceTask`. `ServiceTask` includes factory methods helping to configure request and response params and handlers. If you need more control over the process of making request and handling the response, you can use delegation and implement  `ServiceTaskRetrofitting` protocol and modify task behavior via retrofitter. Also you can subclass `ServiceTask`, it is built for that.  \n\n### Make a GET request expecting json response\n\n```swift\nServiceTask()\n    .url(\"https://host.com/path/to/endpoint\")\n    .method(.GET)\n    .query([\"param\": value])\n    // Expecting valid JSON response\n    .json { (object, response) in\n        print(\"JSON response received: \\(object)\")\n    }\n    .error { (error, response) in\n        print(\"Error occurred: \\(error)\")\n    }\n    .perform()\n```\n\n### Sending and receiving data\n\n\nSend and receive data using objects conforming to Codable protocol:\n\n```swift\n\nstruct NameRequest: Encodable {\n    let name: String\n}\n\nstruct NameResponse: Decodable {\n    let isValid: Bool\n}\n\nServiceTask()\n    // Set base url and HTTP method\n    .endpoint(.POST, \"https://host.com\")\n    // Add path to resource\n    .path(\"/path/to/resource\")\n    // Serialize our Codable struct and set body\n    .body(codable: NameRequest(\"some\"))\n    // Expect response with the object of 'NameResponse' type\n    .codable { (object: NameResponse, response) in\n        print(\"Name valid: \\(object.isValid)\")\n    }\n    // Otherwise will fail with error\n    .error { (error, response) in\n        print(\"Error occured: \\(error)\")\n    }\n    .perform()\n```\n\n\nJust download some file:\n\n```swift\n\nServiceTask()\n    .headers([\"Authorization\": \"Bearer \\(token)\"])\n    .method(.PUT)\n    .url(\"https://host.com/file/12345\")\n    .body(text: \"123456789\")\n    .file { (url, response) in\n        print(\"Downloaded: \\(url)\")\n        // Remove temp file\n        try? FileManager.default.removeItem(at: url)\n    }\n    .error { (error, response) in\n        print(\"Error occured: \\(error)\")\n    }\n    // When download destination not provided, content will be downloaded and saved to temp file\n    .download()\n```\n\n\nUpload multipart form data encoded content:\n\n```swift\n\ndo {\n\n    // Create new form data builder\n    var formDataBuilder = FormDataBuilder()\n\n    // Filename and MIME type will be obtained automatically from URL. It can be provided explicitly too\n    formDataBuilder.append(.file(name: \"image\", url: *url*))\n    \n    // Generate form data in memory. It also can be written directly to disk or stream using encode(to:) method \n    let formData = try formDataBuilder.encode()\n    \n    ServiceTask()\n            .endpoint(.POST, \"https://host.com/upload\")\n            .body(data: formData, contentType: formDataBuilder.contentType)\n            .response(content: { (response) in\n                switch response {\n                case .success:\n                    print(\"Done!\")\n                case .failure(let error):\n                    print(\"Failed to upload: \\(error)\")\n                }\n            })\n            .perform()\n}\ncatch {\n    print(\"\\(error))\n    return\n}\n```\n\nSend and receive Protobuf messages (gRPC over HTTP):\n\n```swift\n\nServiceTask()\n    .endpoint(.POST, \"https://host.com\")\n    // Create and configure request message in place\n    .body { (message: inout Google_Protobuf_StringValue) in\n        message.value = \"something\"\n    }\n    // Expecting Google_Protobuf_Empty message response\n    .proto{ (message: Google_Protobuf_Empty, response) in\n        print(\"Done!\")\n    }\n    .error { (error, response) in\n        print(\"Error occured: \\(error)\")\n    }\n    .perform()\n\n// Or another version of the code above with explicitly provided types\nServiceTask()\n    .endpoint(.POST, \"https://host.com\")\n    // Create and configure request message in place\n    .body(proto: Google_Protobuf_SourceContext.self) { (message) in\n        message.fileName = \"file.name\"\n    }\n    // Expecting Google_Protobuf_Empty message response\n    .response(proto: Google_Protobuf_Empty.self) { (response) in\n        switch response {\n        case .success(let message):\n            print(\"Done!\")\n        case .failure(let error):\n            print(\"Error occured: \\(error)\")\n        }\n    }\n    .perform()\n```\n\n## Author\n\n\nNatan Zalkin natan.zalkin@me.com\n\n## License\n\n\nForest is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCozmonat%2FForest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCozmonat%2FForest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCozmonat%2FForest/lists"}