{"id":21302583,"url":"https://github.com/kevin-lyn/dratini","last_synced_at":"2025-07-11T20:31:49.546Z","repository":{"id":56908734,"uuid":"79209678","full_name":"kevin-lyn/Dratini","owner":"kevin-lyn","description":"Dratini is a neat network abstraction layer.","archived":false,"fork":false,"pushed_at":"2017-09-29T08:34:20.000Z","size":59,"stargazers_count":37,"open_issues_count":3,"forks_count":8,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-03T20:04:54.238Z","etag":null,"topics":["ios","macos","network","network-layer","networking"],"latest_commit_sha":null,"homepage":null,"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/kevin-lyn.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":"2017-01-17T09:14:38.000Z","updated_at":"2022-01-29T17:42:58.000Z","dependencies_parsed_at":"2022-08-21T01:51:00.466Z","dependency_job_id":null,"html_url":"https://github.com/kevin-lyn/Dratini","commit_stats":null,"previous_names":["kevin0571/dratini"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/kevin-lyn/Dratini","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevin-lyn%2FDratini","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevin-lyn%2FDratini/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevin-lyn%2FDratini/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevin-lyn%2FDratini/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kevin-lyn","download_url":"https://codeload.github.com/kevin-lyn/Dratini/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevin-lyn%2FDratini/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264892347,"owners_count":23679275,"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":["ios","macos","network","network-layer","networking"],"created_at":"2024-11-21T15:56:51.219Z","updated_at":"2025-07-11T20:31:49.164Z","avatar_url":"https://github.com/kevin-lyn.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003cimg src=\"https://cloud.githubusercontent.com/assets/1491282/21975961/6d807474-dc0a-11e6-8f36-a71e7f38ee74.png\" height=\"26\" width=\"26\"\u003e Dratini ![CI Status](https://travis-ci.org/kevin0571/Dratini.svg?branch=master) [![codecov](https://codecov.io/gh/kevin0571/Dratini/branch/master/graph/badge.svg)](https://codecov.io/gh/kevin0571/Dratini) ![CocoaPods](http://img.shields.io/cocoapods/v/Dratini.svg?style=flag) ![Carthage](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg) ![Swift Pacakge Manager](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg) ![License](https://img.shields.io/cocoapods/l/Dratini.svg?style=flag)\nDratini is a neat network abstraction layer.  \nIf you are looking for a solution to make your network layer neat, Dratini is your choice.  \nDratini uses protocols to define network request, parameters and response, which makes your network layer more readable and testable.\n\n## Features\n- Protocol base design.\n- Auto serialization for parameters.\n- Response is observable by request id or response type.\n- UI non-blocking since request and response handling happen in background thread.\n- Request and response are interceptable by using delegate.\n- RxSwift extension is available: [RxDratini](https://github.com/kevin0571/RxDratini)\n\n## Requirements\n- Xcode 8.0+\n- Swift 3.0\n\n## Dependencies\n- [Ditto](https://github.com/kevin0571/Ditto): it's used for serializing Swift object into JSON compatible dictionary, mainly used for impelmenting DefaultQueryString, URLEncodedBodyData and JSONBodyData.\n\n## Usage\n\n**CocoaPods**\n```ruby\npod 'Dratini'\n```\n\n**Carthage**\n```ruby\ngithub \"kevin0571/Dratini\"\n```\n\n**Swift Package Manager**\n```ruby\ndependencies: [\n    .Package(url: \"https://github.com/kevin0571/Dratini.git\", majorVersion: 1)\n]\n```\n\n### Overview\nHere are some basic steps to send out a request and observe for its response.\n\nSetup RequestQueue:\n```swift\nlet requestQueue = RequestQueue(baseURL: URL(string: \"http://example.com\"))\n// Delegate and configuration are not required.\n// Set the delegate(RequestQueueDelegate) if you wish to get callbacks for each step.\n// RequestQueueConfiguration.default is used if configuration is not specified.\n```\n\nKeep a shared RequestQueue is recommended:\n```swift\nextension RequestQueue {\n    static let shared = RequestQueue(baseURL: URL(string: \"http://example.com\"))\n}\n```\n\nDescribe your request, parameters and response:\n```swift\nstruct LogInRequest: Request {\n    typealias ParametersType = LogInParameters\n    typealias ResponseType = LogInResponse\n    \n    var parameters: LogInParameters\n    \n    func path() -\u003e String {\n        return \"/login\"\n    }\n    \n    func method() -\u003e HTTPMethod {\n        return .post\n    }\n}\n\n// There are several built-in Parameters types:\n// - DefaultQueryString for query string, it will mostly be used in GET request.\n// - URLEncodedBodyData for URL encoded body data.\n// - JSONBodyData for JSON format body data.\n// - MultipartFormData for multipart form data, it will mostly be used for uploading file.\n//\n// In order to allow you to keep the naming convention of different platforms,\n// property name of DefaultQueryString, URLEncodedBodyData and JSONBodyData will be mapped to other naming convention.\n// By default property will be converted to lowercase name separated by underscore,\n// e.g. accessToken will be converted to access_token. \n// You can set the mapping by overriding \"serializableMapping\" function.\n// See more details in Ditto project's README.\nstruct LogInParameters: URLEncodedBodyData {\n    let username: String\n    let password: String\n}\n\nstruct LogInResponse: Response {\n    let username: String\n    let name: String\n    init?(data: ResponseData, response: URLResponse) {\n        // - Use data.data to access raw response data.\n        // - Use data.jsonObject to access JSON format dictionary.\n        // - Use data.jsonArray to access JSON format array.\n        // - Use data.string to access UTF8 string.\n        guard let username = data.jsonObject[\"username\"] as? String,\n            let name = data.jsonObject[\"name\"] as? String else {\n            return nil\n        }\n        self.username = username\n        self.name = name\n    }\n}\n```\n\nSend the request and observe for response:\n```swift\nlet request = LogInRequest(parameters: LogInParameters(username: username,\n                                                       password: password))\nlet requestID = RequestQueue.shared.add(request)\n// Observe by using requestID.\n// The observer will be removed by RequestQueue after the request is finished.\nrequestQueue.addObserver(for: requestID) { (result: Result\u003cLogInResponse\u003e) in\n    guard let response = result.response else {\n        // Show error message\n        return\n    }\n    // Update UI by using response.username and response.name\n}\n// Observe a specific response type. \n// The observer is owned by an owner. The owner is held weakly by RequestQueue,\n// thus the observer will be removed if owner is released.\nrequestQueue.addObserver(ownedBy: self) { [weak self] (result: Result\u003cLogInResponse\u003e) in\n    // ...\n}\n// NOTE: observer callback is called in main thread.\n```\n\n### Do More with Dratini\nSometimes you need to do more with Dratini, here are some features you might need, e.g. upload file, intercept different states of request and response.\n\nUpload file:\n```swift\nlet data = MultipartFormData()\n// Append file with fileURL\ndata.append(fileURL: fileURL, withName: name, fileName: fileName, mimeType: \"application/x-plist\")  \n// Append raw file data\ndata.append(data: fileData, withName: name, fileName: fileName, mimeType: \"application/x-plist\")\n\n// Assume we've created UploadFileRequest\nlet request = UploadFileRequest(parameters: data)\n// Send out request\n// ...\n```\n\nIntercept states of request:\n```swift\n// Conform to Request with RequestDelegate to get callbacks of different states.\nstruct LogInRequest: Request, RequestDelegate {\n    // ...\n    \n    func requestWillSend(_ urlRequest: inout URLRequest) {\n        // Called before request is sent out.\n        // You are able to modify the URLRequest: update HTTP header for example.\n    }\n    \n    func requestDidSend(_ urlRequest: URLRequest) {\n        // Called after request is sent out.\n    }\n    \n    func request(_ urlRequest: URLRequest, didFailWith error: DRError) {\n        // Called when request is failed to be sent out or response is failed to be created.\n    }\n}\n```\n\nValidate response before creating response and intercept states of response:\n```swift\nstruct LogInResponse: Response, ResponseDelegate {\n    // ...\n    \n    // Validate the response before it's created.\n    static func validate(_ response: URLResponse) -\u003e Bool {\n        guard let httpResponse = response as? HTTPURLResponse else {\n            return true\n        }\n        return httpResponse.statusCode \u003e= 200 \u0026\u0026\n            httpResponse.statusCode \u003c 300 \u0026\u0026\n            httpResponse.allHeaderFields[\"Token\"] != nil\n    }\n    \n    // Called after response is created.\n    func responseDidReceive(_ response: URLResponse) {\n        guard let httpResponse = response as? HTTPURLResponse,\n            let token = httpResponse.allHeaderFields[\"Token\"] else {\n            return nil\n        }\n        // Save your token\n    }\n}\n```\n\nHaving common logic for all requests and response are sometimes necessary, RequestQueueDelegate is here for you:\n```swift\nclass MyRequestQueueDelegate: RequestQueueDelegate {\n    public func requestQueue(_ requestQueue: RequestQueue, willSend request: inout URLRequest) {\n        // Called before each request is sent out.\n    }\n    \n    public func requestQueue(_ requestQueue: RequestQueue, didSend request: URLRequest) {\n        // Called after each request is sent out.\n    }\n    \n    public func requestQueue(_ requestQueue: RequestQueue, didFailWith request: URLRequest, error: DRError) {\n        // Called when request is failed to be sent out or response is failed to be created.\n    }\n    \n    public func requestQueue(_ requestQueue: RequestQueue, didReceive response: URLResponse) {\n        // Called after response is created.\n    }\n}\n\nextension RequestQueue {\n    // Set delegate when creating RequestQueue.\n    static let shared = RequestQueue(delegate: MyRequestQueueDelegate(), baseURL: URL(string: \"http://example.com\")!)\n}\n```\n\nCheck if request is finished and cancel it:\n```swift\nlet isFinished = RequestQueue.shared.isFinished(requestID)\nRequestQueue.shared.cancel(requestID)\n```\n\n### Helpers\n\nWhen you don't really need a Parameters or Response, you can use:\n```swift\nEmptyParameters\nEmptyResponse\n```\n\n### Customization\nIf you wish to customize query string or body data encoding, you can implement your own by adpoting QueryString or BodyData protocol.\n```swift\nstruct MyBodyData: BodyData {\n    let string: String\n    \n    var contentType: String {\n        return \"my-content-type\"\n    }\n    \n    func encode() throws -\u003e Data {\n        return string.data(using: .utf8)!\n    }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevin-lyn%2Fdratini","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkevin-lyn%2Fdratini","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevin-lyn%2Fdratini/lists"}