Skip to content

Commit

Permalink
feat: added convenience decoding strategies (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
philprime authored Dec 6, 2023
1 parent 13c1044 commit 7a33ab6
Show file tree
Hide file tree
Showing 20 changed files with 226 additions and 1 deletion.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,51 @@ struct Request: Postie.Request {
}
```

#### Response parsing strategies

As the response body might differ depending on various factors, you can control the parsing using "decoding strategies".

By default if you use `ResponseBody` to parse the response body, it will use the `DefaultBodyStrategy` (which expects an HTTP status code 2XX or 3XX).

The same applies to the `ResponseErrorBody` to parse the response body for a status code of 400 or above, which is using the `DefaultErrorBodyStrategy`.

For your convenience we added a couple of convenience strategies in `ResponseBody` and `ResponseErrorBody`, you can use with the `ResponseBodyWrapper` and `ResponseErrorBodyWrapper`.

If you want to implement a custom decoding strategy, all you need to do is define a struct implementing the protocol `ResponseBodyDecodingStrategy` or `ResponseErrorBodyDecodingStrategy`.

**Example:***

```swift
struct CustomBodyDecodingStrategy {
public static func allowsEmptyContent(for _: Int) -> Bool {
return false
}

public static func validate(statusCode: Int) -> Bool {
// e.g. only decode if the status code is 999
statusCode == 999
}
}

struct Request: Postie.Request {
struct Response: Decodable {
struct CreatedResponseBody: JSONDecodable {
...
}

@ResponseBody<CreatedResponseBody>.Status201 var createdBody: CreatedResponseBody

struct CustomResponseBody: JSONDecodable {
...
}

@ResponseBodyWrapper<CustomResponseBody, CustomBodyDecodingStrategy> var customBody
}
}
```

Note: Due to technical limitations of the `Codable` protocol in Swift, it is currently not possible to have a non-static/dynamic decoding strategy.

#### Response headers

Use the property wrapper `@ResponseHeader<Strategy>` inside the response type.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
public extension ResponseBody {

typealias OptionalContent = ResponseBodyWrapper<Body, OptionalContentStrategy>

typealias Status200 = ResponseBodyWrapper<Body, ValidateStatus200BodyStrategy>
typealias Status201 = ResponseBodyWrapper<Body, ValidateStatus201BodyStrategy>
typealias Status303 = ResponseBodyWrapper<Body, ValidateStatus303BodyStrategy>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
public extension ResponseErrorBody {
typealias Status400 = ResponseErrorBodyWrapper<Body, ValidateStatus400ErrorBodyStrategy>
typealias Status401 = ResponseErrorBodyWrapper<Body, ValidateStatus401ErrorBodyStrategy>
typealias Status403 = ResponseErrorBodyWrapper<Body, ValidateStatus403ErrorBodyStrategy>
typealias Status404 = ResponseErrorBodyWrapper<Body, ValidateStatus404ErrorBodyStrategy>
typealias Status410 = ResponseErrorBodyWrapper<Body, ValidateStatus410ErrorBodyStrategy>
typealias Status422 = ResponseErrorBodyWrapper<Body, ValidateStatus422ErrorBodyStrategy>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public struct ValidateStatus200BodyStrategy: ResponseBodyDecodingStrategy {
public static func allowsEmptyContent(for _: Int) -> Bool {
return false
}

public static func validate(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.ok.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public struct ValidateStatus201BodyStrategy: ResponseBodyDecodingStrategy {
public static func allowsEmptyContent(for _: Int) -> Bool {
return false
}

public static func validate(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.created.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public struct ValidateStatus303BodyStrategy: ResponseBodyDecodingStrategy {
public static func allowsEmptyContent(for _: Int) -> Bool {
return true
}

public static func validate(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.seeOther.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public struct ValidateStatus400ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy {
public static func isError(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.badRequest.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public struct ValidateStatus401ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy {
public static func isError(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.unauthorized.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public struct ValidateStatus403ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy {
public static func isError(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.forbidden.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public struct ValidateStatus404ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy {
public static func isError(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.notFound.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public struct ValidateStatus410ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy {
public static func isError(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.gone.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public struct ValidateStatus422ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy {
public static func isError(statusCode: Int) -> Bool {
statusCode == HTTPStatusCode.unprocessableEntity.rawValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@testable import Postie
import XCTest

class ValidateStatus200BodyStrategyTests: XCTestCase {
func testAllowsEmptyContent_statusIs200_shouldBeFalse() {
XCTAssertFalse(ValidateStatus200BodyStrategy.allowsEmptyContent(for: 200))
}

func testAllowsEmptyContent_statusIsNot200_shouldBeFalse() {
XCTAssertFalse(ValidateStatus200BodyStrategy.allowsEmptyContent(for: 999))
}

func testValidate_statusIs200_shouldBeTrue() {
XCTAssertTrue(ValidateStatus200BodyStrategy.validate(statusCode: 200))
}

func testValidate_statusIsNot200_shouldBeFalse() {
XCTAssertFalse(ValidateStatus200BodyStrategy.validate(statusCode: 999))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@testable import Postie
import XCTest

class ValidateStatus201BodyStrategyTests: XCTestCase {
func testAllowsEmptyContent_statusIs201_shouldBeFalse() {
XCTAssertFalse(ValidateStatus201BodyStrategy.allowsEmptyContent(for: 201))
}

func testAllowsEmptyContent_statusIsNot201_shouldBeFalse() {
XCTAssertFalse(ValidateStatus201BodyStrategy.allowsEmptyContent(for: 999))
}

func testValidate_statusIs201_shouldBeTrue() {
XCTAssertTrue(ValidateStatus201BodyStrategy.validate(statusCode: 201))
}

func testValidate_statusIsNot201_shouldBeFalse() {
XCTAssertFalse(ValidateStatus201BodyStrategy.validate(statusCode: 999))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@testable import Postie
import XCTest

class ValidateStatus400ErrorBodyStrategyTests: XCTestCase {
func testIsError_statusIs400_shouldBeTrue() {
XCTAssertTrue(ValidateStatus400ErrorBodyStrategy.isError(statusCode: 400))
}

func testIsError_statusIsNot400_shouldBeFalse() {
XCTAssertFalse(ValidateStatus400ErrorBodyStrategy.isError(statusCode: 999))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@testable import Postie
import XCTest

class ValidateStatus401ErrorBodyStrategyTests: XCTestCase {
func testIsError_statusIs401_shouldBeTrue() {
XCTAssertTrue(ValidateStatus401ErrorBodyStrategy.isError(statusCode: 401))
}

func testIsError_statusIsNot401_shouldBeFalse() {
XCTAssertFalse(ValidateStatus401ErrorBodyStrategy.isError(statusCode: 999))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@testable import Postie
import XCTest

class ValidateStatus403ErrorBodyStrategyTests: XCTestCase {
func testIsError_statusIs403_shouldBeTrue() {
XCTAssertTrue(ValidateStatus403ErrorBodyStrategy.isError(statusCode: 403))
}

func testIsError_statusIsNot403_shouldBeFalse() {
XCTAssertFalse(ValidateStatus403ErrorBodyStrategy.isError(statusCode: 999))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@testable import Postie
import XCTest

class ValidateStatus404ErrorBodyStrategyTests: XCTestCase {
func testIsError_statusIs404_shouldBeTrue() {
XCTAssertTrue(ValidateStatus404ErrorBodyStrategy.isError(statusCode: 404))
}

func testIsError_statusIsNot404_shouldBeFalse() {
XCTAssertFalse(ValidateStatus404ErrorBodyStrategy.isError(statusCode: 999))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@testable import Postie
import XCTest

class ValidateStatus410ErrorBodyStrategyTests: XCTestCase {
func testIsError_statusIs410_shouldBeTrue() {
XCTAssertTrue(ValidateStatus410ErrorBodyStrategy.isError(statusCode: 410))
}

func testIsError_statusIsNot410_shouldBeFalse() {
XCTAssertFalse(ValidateStatus410ErrorBodyStrategy.isError(statusCode: 999))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@testable import Postie
import XCTest

class ValidateStatus422ErrorBodyStrategyTests: XCTestCase {
func testIsError_statusIs422_shouldBeTrue() {
XCTAssertTrue(ValidateStatus422ErrorBodyStrategy.isError(statusCode: 422))
}

func testIsError_statusIsNot422_shouldBeFalse() {
XCTAssertFalse(ValidateStatus422ErrorBodyStrategy.isError(statusCode: 999))
}
}

0 comments on commit 7a33ab6

Please sign in to comment.