From 7a33ab6001dbccf930a01311d441c7c8f0be2b2e Mon Sep 17 00:00:00 2001 From: Philip Niedertscheider Date: Wed, 6 Dec 2023 11:11:29 +0100 Subject: [PATCH] feat: added convenience decoding strategies (#38) --- README.md | 45 +++++++++++++++++++ .../ResponseBody+DecodingStrategies.swift | 5 ++- ...ResponseErrorBody+DecodingStrategies.swift | 8 ++++ .../Body/ValidateStatus200BodyStrategy.swift | 9 ++++ .../Body/ValidateStatus201BodyStrategy.swift | 9 ++++ .../Body/ValidateStatus303BodyStrategy.swift | 9 ++++ .../ValidateStatus400ErrorBodyStrategy.swift | 5 +++ .../ValidateStatus401ErrorBodyStrategy.swift | 5 +++ .../ValidateStatus403ErrorBodyStrategy.swift | 5 +++ .../ValidateStatus404ErrorBodyStrategy.swift | 5 +++ .../ValidateStatus410ErrorBodyStrategy.swift | 5 +++ .../ValidateStatus422ErrorBodyStrategy.swift | 5 +++ .../ValidateStatus200BodyStrategyTests.swift | 20 +++++++++ .../ValidateStatus201BodyStrategyTests.swift | 20 +++++++++ ...idateStatus400ErrorBodyStrategyTests.swift | 12 +++++ ...idateStatus401ErrorBodyStrategyTests.swift | 12 +++++ ...idateStatus403ErrorBodyStrategyTests.swift | 12 +++++ ...idateStatus404ErrorBodyStrategyTests.swift | 12 +++++ ...idateStatus410ErrorBodyStrategyTests.swift | 12 +++++ ...idateStatus422ErrorBodyStrategyTests.swift | 12 +++++ 20 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 Sources/Postie/Responses/ResponseErrorBody/ResponseErrorBody+DecodingStrategies.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus200BodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus201BodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus303BodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus400ErrorBodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus401ErrorBodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus403ErrorBodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus404ErrorBodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus410ErrorBodyStrategy.swift create mode 100644 Sources/Postie/Strategies/Body/ValidateStatus422ErrorBodyStrategy.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus200BodyStrategyTests.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus201BodyStrategyTests.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus400ErrorBodyStrategyTests.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus401ErrorBodyStrategyTests.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus403ErrorBodyStrategyTests.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus404ErrorBodyStrategyTests.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus410ErrorBodyStrategyTests.swift create mode 100644 Tests/PostieTests/Strategies/Body/ValidateStatus422ErrorBodyStrategyTests.swift diff --git a/README.md b/README.md index acc075e..5670652 100644 --- a/README.md +++ b/README.md @@ -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.Status201 var createdBody: CreatedResponseBody + + struct CustomResponseBody: JSONDecodable { + ... + } + + @ResponseBodyWrapper 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` inside the response type. diff --git a/Sources/Postie/Responses/ResponseBody/ResponseBody+DecodingStrategies.swift b/Sources/Postie/Responses/ResponseBody/ResponseBody+DecodingStrategies.swift index 1ebee18..32681a7 100644 --- a/Sources/Postie/Responses/ResponseBody/ResponseBody+DecodingStrategies.swift +++ b/Sources/Postie/Responses/ResponseBody/ResponseBody+DecodingStrategies.swift @@ -1,4 +1,7 @@ public extension ResponseBody { - typealias OptionalContent = ResponseBodyWrapper + + typealias Status200 = ResponseBodyWrapper + typealias Status201 = ResponseBodyWrapper + typealias Status303 = ResponseBodyWrapper } diff --git a/Sources/Postie/Responses/ResponseErrorBody/ResponseErrorBody+DecodingStrategies.swift b/Sources/Postie/Responses/ResponseErrorBody/ResponseErrorBody+DecodingStrategies.swift new file mode 100644 index 0000000..3709497 --- /dev/null +++ b/Sources/Postie/Responses/ResponseErrorBody/ResponseErrorBody+DecodingStrategies.swift @@ -0,0 +1,8 @@ +public extension ResponseErrorBody { + typealias Status400 = ResponseErrorBodyWrapper + typealias Status401 = ResponseErrorBodyWrapper + typealias Status403 = ResponseErrorBodyWrapper + typealias Status404 = ResponseErrorBodyWrapper + typealias Status410 = ResponseErrorBodyWrapper + typealias Status422 = ResponseErrorBodyWrapper +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus200BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus200BodyStrategy.swift new file mode 100644 index 0000000..5aa4f1f --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus200BodyStrategy.swift @@ -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 + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus201BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus201BodyStrategy.swift new file mode 100644 index 0000000..4dff553 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus201BodyStrategy.swift @@ -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 + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus303BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus303BodyStrategy.swift new file mode 100644 index 0000000..aeed7dd --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus303BodyStrategy.swift @@ -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 + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus400ErrorBodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus400ErrorBodyStrategy.swift new file mode 100644 index 0000000..7c5f6c0 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus400ErrorBodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus400ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.badRequest.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus401ErrorBodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus401ErrorBodyStrategy.swift new file mode 100644 index 0000000..8905c0d --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus401ErrorBodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus401ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.unauthorized.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus403ErrorBodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus403ErrorBodyStrategy.swift new file mode 100644 index 0000000..8ff6112 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus403ErrorBodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus403ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.forbidden.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus404ErrorBodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus404ErrorBodyStrategy.swift new file mode 100644 index 0000000..9869488 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus404ErrorBodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus404ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.notFound.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus410ErrorBodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus410ErrorBodyStrategy.swift new file mode 100644 index 0000000..d5ccf80 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus410ErrorBodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus410ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.gone.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus422ErrorBodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus422ErrorBodyStrategy.swift new file mode 100644 index 0000000..df2c4a3 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus422ErrorBodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus422ErrorBodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.unprocessableEntity.rawValue + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus200BodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus200BodyStrategyTests.swift new file mode 100644 index 0000000..d817e6a --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus200BodyStrategyTests.swift @@ -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)) + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus201BodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus201BodyStrategyTests.swift new file mode 100644 index 0000000..5a842dc --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus201BodyStrategyTests.swift @@ -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)) + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus400ErrorBodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus400ErrorBodyStrategyTests.swift new file mode 100644 index 0000000..096cbbc --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus400ErrorBodyStrategyTests.swift @@ -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)) + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus401ErrorBodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus401ErrorBodyStrategyTests.swift new file mode 100644 index 0000000..fbb272d --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus401ErrorBodyStrategyTests.swift @@ -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)) + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus403ErrorBodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus403ErrorBodyStrategyTests.swift new file mode 100644 index 0000000..e27647f --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus403ErrorBodyStrategyTests.swift @@ -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)) + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus404ErrorBodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus404ErrorBodyStrategyTests.swift new file mode 100644 index 0000000..a525b3b --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus404ErrorBodyStrategyTests.swift @@ -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)) + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus410ErrorBodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus410ErrorBodyStrategyTests.swift new file mode 100644 index 0000000..1abf469 --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus410ErrorBodyStrategyTests.swift @@ -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)) + } +} diff --git a/Tests/PostieTests/Strategies/Body/ValidateStatus422ErrorBodyStrategyTests.swift b/Tests/PostieTests/Strategies/Body/ValidateStatus422ErrorBodyStrategyTests.swift new file mode 100644 index 0000000..7adf14a --- /dev/null +++ b/Tests/PostieTests/Strategies/Body/ValidateStatus422ErrorBodyStrategyTests.swift @@ -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)) + } +}