diff --git a/Documentation/Advanced.md b/Documentation/Advanced.md deleted file mode 100644 index ffe388b..0000000 --- a/Documentation/Advanced.md +++ /dev/null @@ -1,102 +0,0 @@ -# Advanced Usage # - -What if the endpoints you need to query don't return JSON? Perhaps you have an -endpoint, `GET https://www.example.com/isAuthorized` that returns a `200 OK` -with `"yes"` or `"no"` as a response body. We'll have to do a few things: - -- Define a custom `ResponseParser` to provide to our `Request` object, because -the default one parses `JSON`. -- Define a custom `Deserializer` to provide to our `APIClient`, because the -default one uses `JSONSerialization`. - -We'll start with the custom `Deserializer`. It will simply pass along the -`Data` if it exists: - -```swift -struct DataDeserializer: Deserializer { - func deserialize(_ data: Data?) -> Result { - guard let data = data, !data.isEmpty else { - return .success(NSNull()) - } - - return .success(data) - } -} -``` - -Then, our custom `ResponseParser` will use `String(data:encoding:)` to -transform the incoming `Data` to a `String` representation: - -```swift -import Swish - -enum StringParserError: Error { - case badData(Any) - case invalidUTF8(Data) - case unexpectedResponse(String) -} - -struct StringParser: Parser { - typealias Representation = String - - static func parse(_ object: Any) -> Result { - guard let data = object as? Data else { - return .failure(.parseError(StringParserError.badData(object))) - } - - guard let string = String(data: data, encoding: .utf8) else { - return .failure(.parseError(StringParserError.invalidUTF8(data))) - } - - return .success(string) - } -} -``` - -Finally, our `Request` will convert the `String` into a `Bool`: - -```swift -struct AuthorizedRequest: Request { - typealias ResponseObject = Bool - typealias ResponseParser = StringParser - - func build() -> URLRequest { - let endpoint = URL(string: "https://www.example.com/isAuthorized")! - return URLRequest(url: endpoint) - } - - func parse(_ string: String) -> Result { - switch string { - case "yes": - return .success(true) - case "no": - return .success(false) - default: - return .failure(.parseError(StringParserError.unexpectedResponse(string))) - } - } -} -``` - -Now, we're ready to make our request: - -```swift -let client = APIClient( - requestPerformer: NetworkRequestPerformer(), - deserializer: DataDeserializer() -) -let isAuthorizedRequest = AuthorizedRequest() - -client.perform(isAuthorizedRequest) { result in - switch result { - case .success(true): - // authorized - case .success(false): - // not authorized - case let .failure(error): - // handle the error - } -} -``` - -A simplyfing change we could make would be to implement `Parser` whose `Representation` would be `Bool`, rather than `String`, allowing us to avoid overriding the `parse` function in `AuthorizedRequest`. We'll leave that as an exercise to the reader! diff --git a/Documentation/Basics.md b/Documentation/Basics.md index a2baa12..1dc20a0 100644 --- a/Documentation/Basics.md +++ b/Documentation/Basics.md @@ -1,50 +1,43 @@ -# Basic Usage # - -Let's say you have an endpoint, `GET https://www.example.com/comments/1`, that returns the following JSON: +Let's say we have an endpoint, `GET https://raw.githubusercontent.com/thoughtbot/Swish/master/Documentation/example.json` +that returns the following JSON: ```json { - "id": 1, + "id": 4, "commentText": "Pretty good. Pret-ty pre-ty pre-ty good.", "username": "LarryDavid" } ``` -We'll model this with the following struct, and implement Argo's `Decodable` protocol to tell it how to deal with JSON: +We will model the data with the following struct +and conform to the `Codable` protocol so we can +turn from JSON into a Swift object. ```swift -import Argo -import Curry - -struct Comment { +struct Comment: Codable { let id: Int - let text: String + let body: String let username: String -} -extension Comment: Decodable { - static func decode(_ json: JSON) -> Decoded { - return curry(Comment.init) - <^> j <| "id" - <*> j <| "commentText" - <*> j <| "username" + enum CodingKeys: String, CodingKey { + case id + case body = "commentText" + case username } } ``` -We can model the request by defining a struct that implement's Swish's `Request` protocol: +We can model the request by defining a struct that +implements Swish's `Request` protocol ```swift -import Swish - struct CommentRequest: Request { typealias ResponseObject = Comment - let id: Int func build() -> URLRequest { - let url = URL(string: "https://www.example.com/comments/\(id)")! - return URLRequest(URL: url) + let url = URL(string: "https://raw.githubusercontent.com/thoughtbot/Swish/master/Documentation/example.json")! + return URLRequest(url: url) } } ``` @@ -52,9 +45,7 @@ struct CommentRequest: Request { We can then use the Swish's default `APIClient` to make the request: ```swift -let getComment = CommentRequest(id: 1) - -APIClient().perform(getComment) { (response: Result) in +APIClient().perform(CommentRequest()) { (response: Result) in switch response { case let .success(comment): print("Here's the comment: \(comment)") diff --git a/Documentation/Protocols.md b/Documentation/Protocols.md deleted file mode 100644 index 321cf0d..0000000 --- a/Documentation/Protocols.md +++ /dev/null @@ -1,54 +0,0 @@ -# Protocols # - -This documents each public protocol's intended use and the defaults for each that come built in to Swish. - -At a high level, here's what goes on: - -* A `Client` is told to perform a `Request`, which is done via the `RequestPerformer`. -* The `RequestPerformer` will ensure it has a valid HTTP response code, and if so, hand the `Data` from the `HTTPResponse` to the `Deserializer`. -* The `Deserializer` will convert this `Data` into a `Representation`, which is specified by the `Request`'s `ResponseParser`. The idea here is that the `Deserializer` can convert `Data` into an intermediary form that can then be parsed into the `ResponseObject`. - -In diagram form: - -```swift -Request —— RequestPerformer.perform —————————> Data - —— Deserializer.deserialize —————————> Any - —— Parser.parse —————————————————————> Parser.Representation - —— Request.parse ————————————————————> Request.ResponseObject -``` - -When translated into the concrete types given by the Swish defaults: - -```swift -Request —— NetworkRequestPerformer.perform —————————> Data - —— JSONDeserializer.deserialize ————————————> Any - —— JSON.parse ——————————————————————————————> JSON - —— Request.parse ———————————————————————————> Request.ResponseObject -``` - -### Request ### - -The `Request` protocol models a requestable object (for example, a JSON endpoint). In addition, it defines two associated types: - -- A `ResponseObject` that defines what type of object to expect back from the request. -- A `ResponseParser` that conforms to the `Parser` protocol, which provides a way to go from `Any` into the `Parser.Representation`. - -It is the job of a `Request` to convert from the `Parser.Representation` into the `ResponseObject`. It does the via the `parse` function. - -### Parser ### -The `Parser` protocol defines a way to convert `Any` into a `Representation`. This `Representation` is then fed into the `Request.parse` function to produce a `ResponseObject`. - -### Deserializer ### -The `Deserializer` protocol defines a way to convert `Data?` into `Any`. The `Any` can then be fed into `Request.ResponseParser.parse`. - -### Client ### - -The `Client` protocol defines the core `perform` method that handles tying the other protocols together. It requires an object of type `Deserializer` that is responsible for dealing with the response's `Data`. - -The provided `Client` in Swish is `APIClient`. `APIClient` is instantiated (by default) with a `NetworkRequestPerformer` and a `JSONDeserializer`. - -### RequestPerformer ### - -The `RequestPerformer` protocol defines the layer that actually performs the raw networking. - -The default `RequestPerformer` is `NetworkRequestPerformer` that makes network requests via `URLSession`. diff --git a/Documentation/README.md b/Documentation/README.md index 55c2389..b86659c 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -4,8 +4,3 @@ Swish is a protocol oriented networking library. It aims to be flexible, non-inv ## Basic Usage ## - [Working with JSON Responses](Basics.md) - -## Advanced Usage ## -- [Customizing Swish by implementing protocols](Protocols.md) -- [An example for using Swish with non-JSON responses](Advanced.md) - diff --git a/Documentation/example.json b/Documentation/example.json new file mode 100644 index 0000000..a3f4288 --- /dev/null +++ b/Documentation/example.json @@ -0,0 +1,5 @@ +{ + "id": 4, + "commentText": "Pretty good. Pret-ty pre-ty pre-ty good.", + "username": "LarryDavid" +} diff --git a/README.md b/README.md index d6f7d06..f5227bf 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,18 @@ Nothing but net(working). Swish is a networking library that is particularly meant for requesting and -decoding JSON via [Argo](http://github.com/thoughtbot/Argo). It is protocol -based, and so aims to be easy to test and customize. +decoding JSON via `Decodable`. It is protocol based, and so aims to be easy +to test and customize. ## Version Compatibility Here is the current Swift compatibility breakdown: | Swift Version | Swish Version | -| ------------- | ------------ | -| 3.X | 2.X | -| 2.X | 1.X | +| ------------- | ------------ | +| 4.X | >= 3.0.0 | +| 3.X | > 2.0, < 3.0 | +| 2.X | 1.X | ## Installation @@ -66,8 +67,17 @@ target. ## Usage -- tl;dr: [Basic Usage](Documentation/Basics.md) -- Otherwise, see the [Documentation](Documentation) +### Basic Playground + +You can see an example of Swish in action via the included `SwishExamples.playground`. + +To use that, clone this repository and run `carthage bootstrap --platform iOS`. +When that finishes, open the `Swish.xcworkspace` and click the `SwishExamples` +playground on the left. + +### Documentation + +- [Basic Usage](Documentation/Basics.md) ## License diff --git a/Swish.xcworkspace/contents.xcworkspacedata b/Swish.xcworkspace/contents.xcworkspacedata index 7ab58d3..3219638 100644 --- a/Swish.xcworkspace/contents.xcworkspacedata +++ b/Swish.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,9 @@ + + diff --git a/SwishExamples.playground/Contents.swift b/SwishExamples.playground/Contents.swift new file mode 100644 index 0000000..712e9ad --- /dev/null +++ b/SwishExamples.playground/Contents.swift @@ -0,0 +1,65 @@ +import PlaygroundSupport +import Swish +import Result + +PlaygroundPage.current.needsIndefiniteExecution = true + +/*: overview + +Let's say we have an endpoint, +`GET https://raw.githubusercontent.com/thoughtbot/Swish/master/Documentation/example.json` +that returns the following JSON: + + { + "id": 4, + "commentText": "Pretty good. Pret-ty pre-ty pre-ty good.", + "username": "LarryDavid" + } + +We will model the data with the following struct, +and implement the `Codable` protocol so we can +tell it how to turn from JSON into a Swift object. +*/ + +struct Comment: Codable { + let id: Int + let body: String + let username: String + + enum CodingKeys: String, CodingKey { + case id + case body = "commentText" + case username + } +} + +/*: +We can model the request by defining a struct that +implements Swish's `Request` protocol +*/ + +struct CommentRequest: Request { + typealias ResponseObject = Comment + + func build() -> URLRequest { + let url = URL(string: "https://raw.githubusercontent.com/thoughtbot/Swish/master/Documentation/example.json")! + return URLRequest(url: url) + } +} + +/*: +We can then use the Swish's default APIClient to make the request: +*/ + +APIClient().perform(CommentRequest()) { (response: Result) in + switch response { + case let .success(comment): + print("Here's the comment: \(comment)") + case let .failure(error): + print("Oh no, an error: \(error)") + } +} + +/*: +And that's it! +*/ diff --git a/SwishExamples.playground/contents.xcplayground b/SwishExamples.playground/contents.xcplayground new file mode 100644 index 0000000..89da2d4 --- /dev/null +++ b/SwishExamples.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/SwishExamples.playground/playground.xcworkspace/contents.xcworkspacedata b/SwishExamples.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/SwishExamples.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + +