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..684c231 100644 --- a/Sources/Postie/Responses/ResponseBody/ResponseBody+DecodingStrategies.swift +++ b/Sources/Postie/Responses/ResponseBody/ResponseBody+DecodingStrategies.swift @@ -1,4 +1,6 @@ public extension ResponseBody { - typealias OptionalContent = ResponseBodyWrapper + + typealias Status200 = 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..0668bbe --- /dev/null +++ b/Sources/Postie/Responses/ResponseErrorBody/ResponseErrorBody+DecodingStrategies.swift @@ -0,0 +1,9 @@ +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/ValidateStatus400BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus400BodyStrategy.swift new file mode 100644 index 0000000..0deb6b2 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus400BodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus400BodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.badRequest.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus401BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus401BodyStrategy.swift new file mode 100644 index 0000000..a7f8f9b --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus401BodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus401BodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.unauthorized.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus403BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus403BodyStrategy.swift new file mode 100644 index 0000000..357c383 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus403BodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus403BodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.forbidden.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus404BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus404BodyStrategy.swift new file mode 100644 index 0000000..04904ae --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus404BodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus404BodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.notFound.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus410BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus410BodyStrategy.swift new file mode 100644 index 0000000..bf10b7b --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus410BodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus410BodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.gone.rawValue + } +} diff --git a/Sources/Postie/Strategies/Body/ValidateStatus422BodyStrategy.swift b/Sources/Postie/Strategies/Body/ValidateStatus422BodyStrategy.swift new file mode 100644 index 0000000..5825654 --- /dev/null +++ b/Sources/Postie/Strategies/Body/ValidateStatus422BodyStrategy.swift @@ -0,0 +1,5 @@ +public struct ValidateStatus422BodyStrategy: ResponseErrorBodyDecodingStrategy { + public static func isError(statusCode: Int) -> Bool { + statusCode == HTTPStatusCode.unprocessableEntity.rawValue + } +}