blob: f46ced6b6db7547a6af493a66e27214ead037d3b [file] [log] [blame]
{
"name": "RestingKit",
"version": "0.0.6",
"summary": "Networking made easy.",
"description": "# RestingKit\n\n[![CI Status](https://img.shields.io/travis/moray95/RestingKit.svg?style=flat)](https://travis-ci.org/moray95/RestingKit)\n[![Version](https://img.shields.io/cocoapods/v/RestingKit.svg?style=flat)](https://cocoapods.org/pods/RestingKit)\n[![License](https://img.shields.io/cocoapods/l/RestingKit.svg?style=flat)](https://cocoapods.org/pods/RestingKit)\n[![Platform](https://img.shields.io/cocoapods/p/RestingKit.svg?style=flat)](https://cocoapods.org/pods/RestingKit)\n\n## Introduction\n\nRestingKit is a higher-level wrapper around [Alamofire](https://github.com/Alamofire/Alamofire) and [PromiseKit](https://github.com/mxcl/PromiseKit) written in Swift that allows developers to concentrate on the important stuff instead of writing boiler-plate code for their REST API.\n\n## Features\n\n- Configurable HTTP client (Alamofire is currently the only one provided, but you can write your own!)\n- Path variable expansion powered by [GRMustache.swift](https://github.com/groue/GRMustache.swift)\n- Interception (and modification) of all requests and responses\n\n## Requirements\n\n- iOS 10.0+\n\n- Swift 5.0+\n\n## Installation\n\nRestingKit is available through [CocoaPods](https://cocoapods.org). To install it, simply add the following line to your Podfile:\n\n```ruby\npod 'RestingKit'\n```\n\n## Example project\n\nAn example project is included within the repositry. To run it, first execute `pod install`, then open `RestingKit.xcworkspace`. If you want to test file uploads with the example app, go into the `image_server` directory and run `php -S 0.0.0.0:9000 -c .`, which will start a dummy server for your uploads. The uploads will be stored in the `uploads` directory.\n\n## Usage\n\n### Basic example\n\n1. Create a `RestingClient`\n\n```swift\nimport RestingKit\n\nlet restingClient = RestingClient(baseUrl: \"https://jsonplaceholder.typicode.com\")\n```\n\n`RestingClient` is the core class within RestingKit that does the heavy lifting by executing the requests. It is configured to use a single base URL, so if you need to access multiple APIs, you'll need to create multiple clients.\n\n2. Define your models and endpoints\n\n ```swift\n struct PostCreateModel: Codable {\n let userId: Int\n let title: String\n let body: String\n\n init(userId: Int, title: String, body: String) {\n self.userId = userId\n self.title = title\n self.body = body\n }\n }\n\n struct PostModel: Codable {\n let id: Int\n let userId: Int\n let title: String\n let body: String\n }\n\n let createPostEndpoint = Endpoint<PostCreateModel, PostModel>(.post,\n \"/posts\",\n encoding: .json)\n ```\n\n An `Endpoint` is defined by the models of the request and response, the path (relative to the `RestingClient`'s `baseUrl`), the HTTP method to use and the encoding. If the request doesn't expect any content or doesn't return anything, you can use the special `Nothing` class. Ideally, we would use `Void`, but it is not possible to make it `Encodable` or `Decodable`.\n\n3. Create the request and make the actual call\n\n ```swift\n let postCreateModel = PostCreateModel(userId: 1,\n title: \"Hello world\",\n body: \"Some awesome message\")\n let request = RestingRequest(endpoint createPostEndpoint,\n body: postCreateModel)\n restingClient.perform(request).done { response in\n print(\"Headers: \\(response.headers)\")\n let post = response.body\n print(\"Created post with id: \\(post.id)\")\n }.catch { error in\n print(\"An error occurred: \\(error)\")\n }\n ```\n\n The promise will fail when the server responds with an HTTP status >299, so you don't have to handle this case.\n\n And that's it!\n\n### Handling responses with no content\n\nIf a request might provide a response that might be empty, you can create an `Endpoint` with an optional response type. That way, if the response is empty, `nil` will be returned.\n\n```swift\nlet createPostEndpoint = Endpoint<PostCreateModel, PostModel?>(.post,\n \"/posts\",\n encoding: .json)\nlet postCreateModel = PostCreateModel(userId: 1,\n title: \"Hello world\",\n body: \"Some awesome message\")\nlet request = RestingRequest(endpoint createPostEndpoint,\n body: postCreateModel)\nrestingClient.perform(request).done { response in\n print(\"Headers: \\(response.headers)\")\n if let post = response.body {\n print(\"Created post with id: \\(post.id)\")\n } else {\n print(\"Empty body\")\n }\n}.catch { error in\n print(\"An error occurred: \\(error)\")\n}\n```\n\n**Note:** For this feature to work, the response needs to be truely empty (ie. a content-length of 0). An empty JSON object will produce a decoding error.\n\n### Path variables\n\nThe provided `RestingRequestConverter` allows templating in paths by using [Mustache.swift](https://github.com/groue/GRMustache.swift).\n\n```swift\nlet getPostEndpoint = Endpoint<Nothing, PostModel>(.get,\n \"/posts/{{post_id}}\",\n encoding: .query)\nlet request = RestingRequest(endpoint: getPostEndpoint,\n body: Nothing(),\n pathVariables: [\"post_id\": 1])\n\nrestingClient.perform(request).done { response in\n print(\"Got post: \\(response.body)\")\n}.catch { error in\n print(\"An error occurred: \\(error)\")\n}\n```\n\n### Multipart form data & file upload\n\nIt is possible to perform a multipart form data request with RestingKit. The only thing to make the request is to set the `Endpoint`'s encoding to `.multipartFormData`:\n\n```swift\nlet multipartEndpoint = Endpoint<MyModel, Nothing>(.post,\n \"/some_resource\",\n encoding: .multipartFormData)\n```\n\nNow, each request using this endpoint will be encoded as `multipart/form-data`.\nUploading files is also that easy. You can use the provided `MultipartFile` class within your models, and magically, the file will be uploaded.\n\n```swift\nclass ImageUploadModel: Encodable {\n let file: MultipartFile\n init(imageURL: URL) {\n self.file = MultipartFile(url: imageURL)\n }\n}\n\nlet request = RestingRequest(endpoint: multipartEndpoint,\n body: ImageUploadModel(url: imageUrl))\nrestingClient.upload(request).promise.done { _ in\n print(\"Success!\")\n}.catch {\n print(\"Error: \\($0)\")\n}\n```\n\n**Note:** You should use `upload` methods on the `RestingClient` instead of `perform` when dealing with files and large amounts of data. `perform` will load the whole request body into memory,\nwhile `upload` will store it into a temporary file and stream it without loading into memory.\n\nThe encoding is handled by the `MultipartFormDataEncoder`, which provides an interface and configuration options similar to `JSONEncoder`. You can customize the `MultipartFormDataEncoder`\nused by the `RestingRequestConverter`:\n\n```swift\nlet formDataEncoder = MultipartFormDataEncoder()\nformDataEncoder.keyEncodingStrategy = .convertToSnakeCase\nformDataEncoder.dateEncodingStrategy = .secondsSince1970\nformDataEncoder.dataEncodingStrategy = .raw\nlet configuration = RestingRequestConverter.Configuration(multipartFormDataEncoder: formDataEncoder)\nlet converter = RestingRequestConverter(configuration: configuration)\n```\n\n### Progress handlers\n\n`RestingClient`'s `upload` methods returns a `ProgressablePromise`, which acts like classic promisses but also\naccept a progress handlers.\n\n```swift\nrestingClient.upload(request).progress { progress in\n print(\"Upload \\(progress.fractionCompleted * 100)% completed\")\n}.done { response in\n print(\"Uploaded completed with response: \\(response)\")\n}.catch { error in\n print(\"An error occurred\")\n}\n```\n\n### Interceptors\n\nInterceptors allow to intercept any request and response, and modify it before the request is sent or the response processed. Some basic usages of interceptors include:\n\n- Logging requests and responses\n- Injecting headers\n- Retrying failed requests\n\nTo use interceptors, you will need to implement the `RestingInterceptor` protocol and provide your interceptor to your `RestingClient`.\n\n```swift\nclass LogInterceptor: RestingInterceptor {\n func intercept(request: HTTPRequest, execution: Execution)\n -> ProgressablePromise<HTTPDataResponse> {\n print(\"sending request \\(request)\")\n return execution(request).get { response in\n print(\"got response \\(response)\")\n }\n }\n}\n\nclass DeviceIdInjector: RestingInterceptor {\n func intercept(request: HTTPRequest, execution: Execution) -> ProgressablePromise<HTTPDataResponse> {\n var urlRequest = request.urlRequest\n urlRequest.setValue(UIDevice.current.identifierForVendor?.uuidString,\n forHTTPHeaderField: \"device-id\")\n let request = BasicHTTPRequest(urlRequest: urlRequest, fileUrl: request.fileUrl)\n return execution(request)\n }\n}\n\nlet restingClient = RestingClient(baseUrl: \"https://jsonplaceholder.typicode.com\",\n decoder: decoder,\n requestConverter: requestConverter,\n interceptors: [DeviceIdInjector(), LogInterceptor()])\n```\n\nThe `RestingClient` will pass the request to the interceptors in the provided order, while the response is passed in the reverse order. Therefore, it is important to place `LogInterceptor` at the end of the array (otherwise, it will not be able to log the `device-id` header added by `DeviceIdInjector`).\n\nRestingKit provides an interceptor for logging requests and responses: `RequestResponseLoggingInterceptor`.\n\n**Important**: It is required for each interceptor to call the `execution` parameter, as it is what will run the next interceptors and finally the request. Unless, of course, you do not want to run additional interceptors or send the request.\n\n### Using a custom HTTPClient\n\n`HTTPClient`s are the classes that performs the requests. They take an `HTTPRequest` and return a `(Progressable)Promise<HTTPDataResponse>` without doing anything. `AlamofireClient` is the provided implementation that uses Alamofire to perform the requests and the default client used by `RestingClient`. You can configure a `RestingClient` to use your own implementation:\n\n```swift\nclass MyHTTPClient: HTTPClient {\n public func perform(urlRequest: URLRequest) -> Promise<HTTPDataResponse> {\n // Handle classic request\n }\n func upload(request: HTTPRequest) -> ProgressablePromise<HTTPDataResponse> {\n // Handle uplaod request, with a progress handler\n }\n}\n\nlet restingClient = RestingClient(baseUrl: \"https://jsonplaceholder.typicode.com\",\n httpClient: MyHTTPClient())\n```\n\n### Always-on headers and path variables\n\nSometimes, you need to include the same headers or path variables in all requests. RestingKit has you covered! `RestingRequestConverter` can be configured add headers and include path variables in all requests.\n\nFor example, the `DeviceIdInjector` interceptor can be re-written the following way:\n\n```swift\nlet headerProvider = RestingHeaderProvider(providers: [ \"device-id\": { UIDevice.current.identifierForVendor?.uuidString } ])\n\n// Or\n\nheaderProvider.addHeader(key: \"device-id\") { UIDevice.current.identifierForVendor?.uuidString }\n\n// Or\n\nif let deviceId = UIDevice.current.identifierForVendor?.uuidString {\n headerProvider.addHeader(key: \"device-id\", value: deviceId)\n}\n\nlet configuration = RestingRequestConverter.Configuration(headerProvider: headerProvider)\nlet requestConverter = RestingRequestConverter(configuration: configuration)\n```\n\nNow, `device-id` will be present in all requests.\n\nIn the same way, you can provide path variables:\n\n```swift\nlet pathVariableProvider = RestingPathVariableProvider(providers: [ \"userId\": { AuthManager.user?.id } ])\n\n// Or\n\npathVariableProvider.addVariable(key: \"user-id\") { AuthManager.user?.id }\n\n// Or\n\nif let userId = AuthManager.user?.id {\n pathVariableProvider.addVariable(key: \"user-id\", value: userId)\n}\n\nlet configuration = RestingRequestConverter.Configuration(pathVariableProvider: pathVariableProvider)\nlet requestConverter = RestingRequestConverter(configuration: configuration)\n```\n\nYou will now be able to use a path like `/users/{{userId}}/info` without providing the `userId` to the `RestingRequest`.\n\nWhen using header and path variable providers, the values added to individual requests overrides the one provided by providers.\n\n## Work in progress\n\nAs RestingKit is still new and in development, there are some missing features that needs implementation:\n\n- File downloads\n- Any other feature you might request!\n\nAdditionally, there might be some api-breaking changes until the project reaches full maturity.\n\n## Contributing\n\nIf you need help with getting started or have a feature request, just open up an issue. Pull requests are also welcome for bug fixes and new features.\n\n## Author\n\nMoray Baruh\n\n## License\n\nRestingKit is available under the MIT license. See the LICENSE file for more info.",
"homepage": "https://github.com/moray95/RestingKit",
"license": {
"type": "MIT",
"file": "LICENSE"
},
"authors": {
"Moray Baruh": "contact@moraybaruh.com"
},
"source": {
"git": "https://github.com/moray95/RestingKit.git",
"tag": "0.0.6"
},
"platforms": {
"ios": "10.0"
},
"source_files": [
"RestingKit/*.swift",
"RestingKit/**/*.swift"
],
"swift_versions": [
"5.0"
],
"dependencies": {
"Alamofire": [
"~> 4.8"
],
"GRMustache.swift": [
"~> 4.0"
],
"PromiseKit": [
"~> 6.8"
]
},
"swift_version": "5.0"
}