From cf3c665082fe97e10fd0748b41d909443e5d284b Mon Sep 17 00:00:00 2001 From: Loay Ashraf Date: Fri, 17 May 2024 15:53:07 +0300 Subject: [PATCH] Feature/merge core http (#77) * feat: move CoreHTTP contents to Sources folder Resolves: none. * Update Package.swift * Update Package.swift * Update Package.resolved * Update Package.resolved * Update Completable+Retry.swift * feat: update dummy Resolves: none. --- .../SPMDummy.xcodeproj/project.pbxproj | 2 + .../xcshareddata/swiftpm/Package.resolved | 9 - .../iOS/iOS Example.xcodeproj/project.pbxproj | 2 + .../macOS Example.xcodeproj/project.pbxproj | 2 + Package.resolved | 9 - Package.swift | 5 +- RxNetworkKit.xcodeproj/project.pbxproj | 151 ++++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 10 +- .../CoreExample.xcodeproj/project.pbxproj | 2 + .../Request/Error/DefaultHTTPAPIError.swift | 14 ++ .../Request/Error/DefaultHTTPBodyError.swift | 20 ++ .../Common/Request/Error/HTTPAPIError.swift | 9 + .../Common/Request/Error/HTTPBodyError.swift | 11 + .../Request/Error/HTTPClientError.swift | 13 ++ Source/Common/Request/Error/HTTPError.swift | 41 ++++ .../Request/Error/HTTPServerError.swift | 12 ++ .../Interceptor/HTTPRequestAdapter.swift | 22 ++ .../Interceptor/HTTPRequestInterceptor.swift | 9 + .../Interceptor/HTTPRequestRetrier.swift | 41 ++++ .../Interceptor/HTTPRequestRetryPolicy.swift | 38 ++++ .../Request/Parameters/HTTPMethod.swift | 18 ++ .../Request/Parameters/HTTPScheme.swift | 12 ++ .../Request/Response/HTTPStatusCode.swift | 193 ++++++++++++++++++ .../Response/HTTPURLResponse+StatusCode.swift | 14 ++ .../Request/Router/HTTPRequestRouter.swift | 69 +++++++ .../REST/Extensions/Completable+Retry.swift | 1 - Source/RxNetworkKit.swift | 8 - 27 files changed, 677 insertions(+), 60 deletions(-) create mode 100644 Source/Common/Request/Error/DefaultHTTPAPIError.swift create mode 100644 Source/Common/Request/Error/DefaultHTTPBodyError.swift create mode 100644 Source/Common/Request/Error/HTTPAPIError.swift create mode 100644 Source/Common/Request/Error/HTTPBodyError.swift create mode 100644 Source/Common/Request/Error/HTTPClientError.swift create mode 100644 Source/Common/Request/Error/HTTPError.swift create mode 100644 Source/Common/Request/Error/HTTPServerError.swift create mode 100644 Source/Common/Request/Interceptor/HTTPRequestAdapter.swift create mode 100644 Source/Common/Request/Interceptor/HTTPRequestInterceptor.swift create mode 100644 Source/Common/Request/Interceptor/HTTPRequestRetrier.swift create mode 100644 Source/Common/Request/Interceptor/HTTPRequestRetryPolicy.swift create mode 100644 Source/Common/Request/Parameters/HTTPMethod.swift create mode 100644 Source/Common/Request/Parameters/HTTPScheme.swift create mode 100644 Source/Common/Request/Response/HTTPStatusCode.swift create mode 100644 Source/Common/Request/Response/HTTPURLResponse+StatusCode.swift create mode 100644 Source/Common/Request/Router/HTTPRequestRouter.swift delete mode 100644 Source/RxNetworkKit.swift diff --git a/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.pbxproj b/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.pbxproj index 471c1dc..e459789 100644 --- a/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.pbxproj +++ b/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.pbxproj @@ -338,6 +338,7 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; @@ -372,6 +373,7 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; diff --git a/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8324ff6..3d62ac3 100644 --- a/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/.github/workflows/SPMDummy/SPMDummy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "CoreHTTP", - "repositoryURL": "https://github.com/loay-ashraf/CoreHTTP", - "state": { - "branch": null, - "revision": "17b030a4a10ac9449e6c107bce13f112280aae6b", - "version": "1.0.0" - } - }, { "package": "RxSwift", "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", diff --git a/Examples/iOS/iOS Example.xcodeproj/project.pbxproj b/Examples/iOS/iOS Example.xcodeproj/project.pbxproj index 87897ab..0f764a3 100644 --- a/Examples/iOS/iOS Example.xcodeproj/project.pbxproj +++ b/Examples/iOS/iOS Example.xcodeproj/project.pbxproj @@ -338,6 +338,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -375,6 +376,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Examples/macOS/macOS Example.xcodeproj/project.pbxproj b/Examples/macOS/macOS Example.xcodeproj/project.pbxproj index 078be98..acfca37 100644 --- a/Examples/macOS/macOS Example.xcodeproj/project.pbxproj +++ b/Examples/macOS/macOS Example.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ /* Begin PBXFileReference section */ C623E7182AD6117200A20A0A /* macOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "macOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + C625C4D92BF7791A00761FD6 /* CoreHTTP.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreHTTP.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C63975182A95166A00D0AC13 /* TableCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellView.swift; sourceTree = ""; }; C63EEB3D2AD7C29F003A64CA /* CoreExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C63EEB402AD7C2A4003A64CA /* RxNetworkKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RxNetworkKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -92,6 +93,7 @@ C63EEAFF2AD7A40E003A64CA /* Frameworks */ = { isa = PBXGroup; children = ( + C625C4D92BF7791A00761FD6 /* CoreHTTP.framework */, C63EEB402AD7C2A4003A64CA /* RxNetworkKit.framework */, C63EEB3D2AD7C29F003A64CA /* CoreExample.framework */, ); diff --git a/Package.resolved b/Package.resolved index 96e6e94..36e78b6 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "CoreHTTP", - "repositoryURL": "https://github.com/loay-ashraf/CoreHTTP", - "state": { - "branch": null, - "revision": "17b030a4a10ac9449e6c107bce13f112280aae6b", - "version": "1.0.0" - } - }, { "package": "RxSwift", "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", diff --git a/Package.swift b/Package.swift index 866fb2c..f676664 100644 --- a/Package.swift +++ b/Package.swift @@ -18,11 +18,10 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.6.0")), - .package(url: "https://github.com/RxSwiftCommunity/RxSwiftExt", .upToNextMajor(from: "6.2.0")), - .package(url: "https://github.com/loay-ashraf/CoreHTTP", .upToNextMajor(from: "2.0.0")) + .package(url: "https://github.com/RxSwiftCommunity/RxSwiftExt", .upToNextMajor(from: "6.2.0")) ], targets: [ - .target(name: "RxNetworkKit", dependencies: ["RxSwift", "RxSwiftExt", .product(name: "RxCocoa", package: "RxSwift"), "CoreHTTP"], path: "Source"), + .target(name: "RxNetworkKit", dependencies: ["RxSwift", "RxSwiftExt", .product(name: "RxCocoa", package: "RxSwift")], path: "Source"), ], swiftLanguageVersions: [.v5] ) diff --git a/RxNetworkKit.xcodeproj/project.pbxproj b/RxNetworkKit.xcodeproj/project.pbxproj index 0f78731..c66ed6b 100644 --- a/RxNetworkKit.xcodeproj/project.pbxproj +++ b/RxNetworkKit.xcodeproj/project.pbxproj @@ -49,11 +49,9 @@ C61A7E302B627B3000407C38 /* SessionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61A7E2F2B627B3000407C38 /* SessionConfiguration.swift */; }; C61A7E322B62885F00407C38 /* URLSession+LogRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61A7E312B62885F00407C38 /* URLSession+LogRequests.swift */; }; C61CB5642AF2CFDD006A203A /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61CB5632AF2CFDD006A203A /* Session.swift */; }; - C623E7B42AD6262A00A20A0A /* CoreHTTP in Frameworks */ = {isa = PBXBuildFile; productRef = C623E7B32AD6262A00A20A0A /* CoreHTTP */; }; C6513B812BF76C7000A19EBC /* WebSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6513B802BF76C7000A19EBC /* WebSocketClient.swift */; }; C6554A2B2AD5BBB60090DD3A /* RESTClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6554A2A2AD5BBB60090DD3A /* RESTClient.swift */; }; C6554A2D2AD5C1560090DD3A /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6554A2C2AD5C1560090DD3A /* HTTPClient.swift */; }; - C658CF652AD5F648009E561D /* RxNetworkKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C658CF642AD5F648009E561D /* RxNetworkKit.swift */; }; C69A78562ACF001400ECF092 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = C69A78552ACEFF3200ECF092 /* Docs.docc */; }; C6A9BEFA2A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9BEF92A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift */; }; C6A9BEFD2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9BEFC2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift */; }; @@ -76,6 +74,22 @@ C6C643112BE6C9EC0071C2CC /* Set+SecCertificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C643102BE6C9EC0071C2CC /* Set+SecCertificate.swift */; }; C6C643132BE6CA080071C2CC /* Set+SecKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C643122BE6CA080071C2CC /* Set+SecKey.swift */; }; C6C643152BE6CA830071C2CC /* Data+SecCertificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C643142BE6CA830071C2CC /* Data+SecCertificate.swift */; }; + C6EAFAE22BF77B00008D3C2B /* DefaultHTTPAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFACC2BF77B00008D3C2B /* DefaultHTTPAPIError.swift */; }; + C6EAFAE32BF77B00008D3C2B /* DefaultHTTPBodyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFACD2BF77B00008D3C2B /* DefaultHTTPBodyError.swift */; }; + C6EAFAE42BF77B00008D3C2B /* HTTPAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFACE2BF77B00008D3C2B /* HTTPAPIError.swift */; }; + C6EAFAE52BF77B00008D3C2B /* HTTPBodyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFACF2BF77B00008D3C2B /* HTTPBodyError.swift */; }; + C6EAFAE62BF77B00008D3C2B /* HTTPClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD02BF77B00008D3C2B /* HTTPClientError.swift */; }; + C6EAFAE72BF77B00008D3C2B /* HTTPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD12BF77B00008D3C2B /* HTTPError.swift */; }; + C6EAFAE82BF77B00008D3C2B /* HTTPServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD22BF77B00008D3C2B /* HTTPServerError.swift */; }; + C6EAFAE92BF77B00008D3C2B /* HTTPRequestAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD42BF77B00008D3C2B /* HTTPRequestAdapter.swift */; }; + C6EAFAEA2BF77B00008D3C2B /* HTTPRequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD52BF77B00008D3C2B /* HTTPRequestInterceptor.swift */; }; + C6EAFAEB2BF77B00008D3C2B /* HTTPRequestRetrier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD62BF77B00008D3C2B /* HTTPRequestRetrier.swift */; }; + C6EAFAEC2BF77B00008D3C2B /* HTTPRequestRetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD72BF77B00008D3C2B /* HTTPRequestRetryPolicy.swift */; }; + C6EAFAED2BF77B00008D3C2B /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFAD92BF77B00008D3C2B /* HTTPMethod.swift */; }; + C6EAFAEE2BF77B00008D3C2B /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFADA2BF77B00008D3C2B /* HTTPScheme.swift */; }; + C6EAFAEF2BF77B00008D3C2B /* HTTPStatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFADC2BF77B00008D3C2B /* HTTPStatusCode.swift */; }; + C6EAFAF02BF77B00008D3C2B /* HTTPURLResponse+StatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFADD2BF77B00008D3C2B /* HTTPURLResponse+StatusCode.swift */; }; + C6EAFAF12BF77B00008D3C2B /* HTTPRequestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EAFADF2BF77B00008D3C2B /* HTTPRequestRouter.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -119,10 +133,10 @@ C61A7E2F2B627B3000407C38 /* SessionConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionConfiguration.swift; sourceTree = ""; }; C61A7E312B62885F00407C38 /* URLSession+LogRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+LogRequests.swift"; sourceTree = ""; }; C61CB5632AF2CFDD006A203A /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; + C625C4D52BF7786E00761FD6 /* CoreHTTP.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreHTTP.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C6513B802BF76C7000A19EBC /* WebSocketClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketClient.swift; sourceTree = ""; }; C6554A2A2AD5BBB60090DD3A /* RESTClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTClient.swift; sourceTree = ""; }; C6554A2C2AD5C1560090DD3A /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; - C658CF642AD5F648009E561D /* RxNetworkKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxNetworkKit.swift; sourceTree = ""; }; C69A78552ACEFF3200ECF092 /* Docs.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Docs.docc; sourceTree = ""; }; C6A9BEF92A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+setAdditionalHTTPHeader.swift"; sourceTree = ""; }; C6A9BEFC2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+setUserAgentHTTPHeader.swift"; sourceTree = ""; }; @@ -145,6 +159,22 @@ C6C643102BE6C9EC0071C2CC /* Set+SecCertificate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+SecCertificate.swift"; sourceTree = ""; }; C6C643122BE6CA080071C2CC /* Set+SecKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+SecKey.swift"; sourceTree = ""; }; C6C643142BE6CA830071C2CC /* Data+SecCertificate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+SecCertificate.swift"; sourceTree = ""; }; + C6EAFACC2BF77B00008D3C2B /* DefaultHTTPAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultHTTPAPIError.swift; sourceTree = ""; }; + C6EAFACD2BF77B00008D3C2B /* DefaultHTTPBodyError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultHTTPBodyError.swift; sourceTree = ""; }; + C6EAFACE2BF77B00008D3C2B /* HTTPAPIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPAPIError.swift; sourceTree = ""; }; + C6EAFACF2BF77B00008D3C2B /* HTTPBodyError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPBodyError.swift; sourceTree = ""; }; + C6EAFAD02BF77B00008D3C2B /* HTTPClientError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPClientError.swift; sourceTree = ""; }; + C6EAFAD12BF77B00008D3C2B /* HTTPError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPError.swift; sourceTree = ""; }; + C6EAFAD22BF77B00008D3C2B /* HTTPServerError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPServerError.swift; sourceTree = ""; }; + C6EAFAD42BF77B00008D3C2B /* HTTPRequestAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPRequestAdapter.swift; sourceTree = ""; }; + C6EAFAD52BF77B00008D3C2B /* HTTPRequestInterceptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPRequestInterceptor.swift; sourceTree = ""; }; + C6EAFAD62BF77B00008D3C2B /* HTTPRequestRetrier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPRequestRetrier.swift; sourceTree = ""; }; + C6EAFAD72BF77B00008D3C2B /* HTTPRequestRetryPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPRequestRetryPolicy.swift; sourceTree = ""; }; + C6EAFAD92BF77B00008D3C2B /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; + C6EAFADA2BF77B00008D3C2B /* HTTPScheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPScheme.swift; sourceTree = ""; }; + C6EAFADC2BF77B00008D3C2B /* HTTPStatusCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPStatusCode.swift; sourceTree = ""; }; + C6EAFADD2BF77B00008D3C2B /* HTTPURLResponse+StatusCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HTTPURLResponse+StatusCode.swift"; sourceTree = ""; }; + C6EAFADF2BF77B00008D3C2B /* HTTPRequestRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPRequestRouter.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -154,7 +184,6 @@ files = ( 0B77E0BD29D968DE0077FBC0 /* RxSwift in Frameworks */, 0B77E0BB29D968DE0077FBC0 /* RxRelay in Frameworks */, - C623E7B42AD6262A00A20A0A /* CoreHTTP in Frameworks */, 0B77E0C029D969370077FBC0 /* RxSwiftExt in Frameworks */, 0B77E0B929D968DE0077FBC0 /* RxCocoa in Frameworks */, ); @@ -170,6 +199,7 @@ C69A78552ACEFF3200ECF092 /* Docs.docc */, 0B77E04A29D965D30077FBC0 /* Source */, 0B77DFD729D964D40077FBC0 /* Products */, + C625C4D42BF7786E00761FD6 /* Frameworks */, ); sourceTree = ""; }; @@ -190,8 +220,8 @@ 0B77E06129D965D30077FBC0 /* HTTP */, C6513B7B2BF76BDC00A19EBC /* WebSocket */, 0B77E06729D965D30077FBC0 /* Reachability */, + C625C4DC2BF77A2400761FD6 /* Common */, C6049B152A95307800E5727E /* RxNetworkKit.h */, - C658CF642AD5F648009E561D /* RxNetworkKit.swift */, ); path = Source; sourceTree = ""; @@ -254,6 +284,22 @@ path = Session; sourceTree = ""; }; + C625C4D42BF7786E00761FD6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C625C4D52BF7786E00761FD6 /* CoreHTTP.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C625C4DC2BF77A2400761FD6 /* Common */ = { + isa = PBXGroup; + children = ( + C6EAFAE12BF77B00008D3C2B /* Request */, + ); + path = Common; + sourceTree = ""; + }; C6513B7B2BF76BDC00A19EBC /* WebSocket */ = { isa = PBXGroup; children = ( @@ -451,6 +497,69 @@ path = Types; sourceTree = ""; }; + C6EAFAD32BF77B00008D3C2B /* Error */ = { + isa = PBXGroup; + children = ( + C6EAFACC2BF77B00008D3C2B /* DefaultHTTPAPIError.swift */, + C6EAFACD2BF77B00008D3C2B /* DefaultHTTPBodyError.swift */, + C6EAFACE2BF77B00008D3C2B /* HTTPAPIError.swift */, + C6EAFACF2BF77B00008D3C2B /* HTTPBodyError.swift */, + C6EAFAD02BF77B00008D3C2B /* HTTPClientError.swift */, + C6EAFAD12BF77B00008D3C2B /* HTTPError.swift */, + C6EAFAD22BF77B00008D3C2B /* HTTPServerError.swift */, + ); + path = Error; + sourceTree = ""; + }; + C6EAFAD82BF77B00008D3C2B /* Interceptor */ = { + isa = PBXGroup; + children = ( + C6EAFAD42BF77B00008D3C2B /* HTTPRequestAdapter.swift */, + C6EAFAD52BF77B00008D3C2B /* HTTPRequestInterceptor.swift */, + C6EAFAD62BF77B00008D3C2B /* HTTPRequestRetrier.swift */, + C6EAFAD72BF77B00008D3C2B /* HTTPRequestRetryPolicy.swift */, + ); + path = Interceptor; + sourceTree = ""; + }; + C6EAFADB2BF77B00008D3C2B /* Parameters */ = { + isa = PBXGroup; + children = ( + C6EAFAD92BF77B00008D3C2B /* HTTPMethod.swift */, + C6EAFADA2BF77B00008D3C2B /* HTTPScheme.swift */, + ); + path = Parameters; + sourceTree = ""; + }; + C6EAFADE2BF77B00008D3C2B /* Response */ = { + isa = PBXGroup; + children = ( + C6EAFADC2BF77B00008D3C2B /* HTTPStatusCode.swift */, + C6EAFADD2BF77B00008D3C2B /* HTTPURLResponse+StatusCode.swift */, + ); + path = Response; + sourceTree = ""; + }; + C6EAFAE02BF77B00008D3C2B /* Router */ = { + isa = PBXGroup; + children = ( + C6EAFADF2BF77B00008D3C2B /* HTTPRequestRouter.swift */, + ); + path = Router; + sourceTree = ""; + }; + C6EAFAE12BF77B00008D3C2B /* Request */ = { + isa = PBXGroup; + children = ( + C6EAFAD32BF77B00008D3C2B /* Error */, + C6EAFAD82BF77B00008D3C2B /* Interceptor */, + C6EAFADB2BF77B00008D3C2B /* Parameters */, + C6EAFADE2BF77B00008D3C2B /* Response */, + C6EAFAE02BF77B00008D3C2B /* Router */, + ); + path = Request; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -484,7 +593,6 @@ 0B77E0BA29D968DE0077FBC0 /* RxRelay */, 0B77E0BC29D968DE0077FBC0 /* RxSwift */, 0B77E0BF29D969370077FBC0 /* RxSwiftExt */, - C623E7B32AD6262A00A20A0A /* CoreHTTP */, ); productName = RxNetworkKit; productReference = 0B77DFD629D964D40077FBC0 /* RxNetworkKit.framework */; @@ -516,7 +624,6 @@ packageReferences = ( 0B77E0B729D968DE0077FBC0 /* XCRemoteSwiftPackageReference "RxSwift" */, 0B77E0BE29D969370077FBC0 /* XCRemoteSwiftPackageReference "RxSwiftExt" */, - C623E7B22AD6262A00A20A0A /* XCRemoteSwiftPackageReference "CoreHTTP" */, ); productRefGroup = 0B77DFCC29D964D40077FBC0; projectDirPath = ""; @@ -548,37 +655,50 @@ C6BDFFEA2ACDF3D90022F675 /* Reactive+WebSocketSend.swift in Sources */, 0B77E09F29D965D30077FBC0 /* NWPath+InterfaceType.swift in Sources */, 0B77E0A029D965D30077FBC0 /* NetworkReachabilityStatus.swift in Sources */, + C6EAFAEC2BF77B00008D3C2B /* HTTPRequestRetryPolicy.swift in Sources */, 0B77E0AE29D965D30077FBC0 /* Single+VerifyResponse.swift in Sources */, + C6EAFAED2BF77B00008D3C2B /* HTTPMethod.swift in Sources */, C6C643092BE6C9340071C2CC /* SecCertificate+Bundle.swift in Sources */, 0B77E0B429D965D30077FBC0 /* HTTPDownloadRequestRouter.swift in Sources */, C6C643132BE6CA080071C2CC /* Set+SecKey.swift in Sources */, 0B77E09A29D965D30077FBC0 /* HTTPMIMEType.swift in Sources */, - C658CF652AD5F648009E561D /* RxNetworkKit.swift in Sources */, C6A9BEFF2A93FB1D00459E32 /* ProcessInfo+operatingSystemName.swift in Sources */, + C6EAFAE72BF77B00008D3C2B /* HTTPError.swift in Sources */, + C6EAFAF02BF77B00008D3C2B /* HTTPURLResponse+StatusCode.swift in Sources */, C6C643032BE6C8580071C2CC /* TLSTrustEvaluatorConfiguration.swift in Sources */, 0B77E08E29D965D30077FBC0 /* HTTPUploadRequestFile.swift in Sources */, 0B77E08D29D965D30077FBC0 /* HTTPUploadRequestFormData.swift in Sources */, C61A7E262B62782D00407C38 /* Reactive+RESTResponse.swift in Sources */, + C6EAFAEE2BF77B00008D3C2B /* HTTPScheme.swift in Sources */, 0B77E0AF29D965D30077FBC0 /* Single+Decodable.swift in Sources */, C69A78562ACF001400ECF092 /* Docs.docc in Sources */, 0B77E0A129D965D30077FBC0 /* NWInterfaceType+RawRepresentable.swift in Sources */, 0B77E0B129D965D30077FBC0 /* Observable+Decodable.swift in Sources */, C6C6430F2BE6C9A40071C2CC /* SecKey+Bundle.swift in Sources */, + C6EAFAEA2BF77B00008D3C2B /* HTTPRequestInterceptor.swift in Sources */, C6C643152BE6CA830071C2CC /* Data+SecCertificate.swift in Sources */, C6513B812BF76C7000A19EBC /* WebSocketClient.swift in Sources */, 0B77E08A29D965D30077FBC0 /* HTTPDownloadRequestEvent.swift in Sources */, + C6EAFAE52BF77B00008D3C2B /* HTTPBodyError.swift in Sources */, C6A9BEFD2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift in Sources */, + C6EAFAEB2BF77B00008D3C2B /* HTTPRequestRetrier.swift in Sources */, C6554A2B2AD5BBB60090DD3A /* RESTClient.swift in Sources */, + C6EAFAE82BF77B00008D3C2B /* HTTPServerError.swift in Sources */, C6C643052BE6C8770071C2CC /* TLSTrustEvaluationPolicy.swift in Sources */, 0B77E09129D965D30077FBC0 /* Reactive+URLSessionAdaptedUploadResponse.swift in Sources */, + C6EAFAE22BF77B00008D3C2B /* DefaultHTTPAPIError.swift in Sources */, C6C643012BE6C7B30071C2CC /* TLSTrustEvaluator.swift in Sources */, C6A9BEFA2A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift in Sources */, + C6EAFAE32BF77B00008D3C2B /* DefaultHTTPBodyError.swift in Sources */, + C6EAFAEF2BF77B00008D3C2B /* HTTPStatusCode.swift in Sources */, C6BDFFF32ACDF5100022F675 /* WebSocketMessage.swift in Sources */, C61A7E242B6276F800407C38 /* HTTPRequestLogger.swift in Sources */, C6C6430B2BE6C9510071C2CC /* SecCertificate+PublicKey.swift in Sources */, C61A7E302B627B3000407C38 /* SessionConfiguration.swift in Sources */, + C6EAFAE42BF77B00008D3C2B /* HTTPAPIError.swift in Sources */, C6B4B4C42AD47A2F009073ED /* WebSocketError.swift in Sources */, 0B77E0A429D965D30077FBC0 /* NetworkReachability.swift in Sources */, + C6EAFAE62BF77B00008D3C2B /* HTTPClientError.swift in Sources */, 0B77E0B229D965D30077FBC0 /* Completable+Retry.swift in Sources */, 0B77E0B029D965D30077FBC0 /* Observable+Retry.swift in Sources */, 0B77E08B29D965D30077FBC0 /* URLSession+DownloadTask.swift in Sources */, @@ -595,6 +715,7 @@ 0B77E09329D965D30077FBC0 /* Reactive+URLSessionUploadResponse.swift in Sources */, 0B77E0AC29D965D30077FBC0 /* Single+Decode.swift in Sources */, C61A7E2E2B6279C700407C38 /* Data+JSON.swift in Sources */, + C6EAFAF12BF77B00008D3C2B /* HTTPRequestRouter.swift in Sources */, 0B77E0A329D965D30077FBC0 /* NetworkInterfaceType.swift in Sources */, C6C6430D2BE6C98B0071C2CC /* SecKey+ExternalRepresentation.swift in Sources */, 0B77E09229D965D30077FBC0 /* HTTPUploadRequestEvent.swift in Sources */, @@ -604,6 +725,7 @@ 0B77E08929D965D30077FBC0 /* Reactive+URLSessionDownloadResponse.swift in Sources */, C6554A2D2AD5C1560090DD3A /* HTTPClient.swift in Sources */, C61A7E2A2B62794900407C38 /* URLSession+OutgoingRequest.swift in Sources */, + C6EAFAE92BF77B00008D3C2B /* HTTPRequestAdapter.swift in Sources */, C6BDFFEC2ACDF4100022F675 /* Reactive+WebSocketPing.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -861,14 +983,6 @@ minimumVersion = 6.2.0; }; }; - C623E7B22AD6262A00A20A0A /* XCRemoteSwiftPackageReference "CoreHTTP" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/loay-ashraf/CoreHTTP"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -892,11 +1006,6 @@ package = 0B77E0BE29D969370077FBC0 /* XCRemoteSwiftPackageReference "RxSwiftExt" */; productName = RxSwiftExt; }; - C623E7B32AD6262A00A20A0A /* CoreHTTP */ = { - isa = XCSwiftPackageProductDependency; - package = C623E7B22AD6262A00A20A0A /* XCRemoteSwiftPackageReference "CoreHTTP" */; - productName = CoreHTTP; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0B77DFCD29D964D40077FBC0 /* Project object */; diff --git a/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved b/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved index e39f16b..747a0f7 100644 --- a/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,14 +1,6 @@ { + "originHash" : "0de4ac07bf4deb2bff179bbf81665f083753da360913fe8b8ff4a56936e57591", "pins" : [ - { - "identity" : "corehttp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/loay-ashraf/CoreHTTP", - "state" : { - "revision" : "d38c3063412e7121bbb5a1fcef3089d847865266", - "version" : "2.0.1" - } - }, { "identity" : "rxdatasources", "kind" : "remoteSourceControl", diff --git a/Shared/CoreExample/CoreExample.xcodeproj/project.pbxproj b/Shared/CoreExample/CoreExample.xcodeproj/project.pbxproj index f00fef8..962e944 100644 --- a/Shared/CoreExample/CoreExample.xcodeproj/project.pbxproj +++ b/Shared/CoreExample/CoreExample.xcodeproj/project.pbxproj @@ -375,6 +375,7 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -420,6 +421,7 @@ SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; diff --git a/Source/Common/Request/Error/DefaultHTTPAPIError.swift b/Source/Common/Request/Error/DefaultHTTPAPIError.swift new file mode 100644 index 0000000..4fa505b --- /dev/null +++ b/Source/Common/Request/Error/DefaultHTTPAPIError.swift @@ -0,0 +1,14 @@ +// +// DefaultHTTPAPIError.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 19/02/2023. +// + +/// Default type used for decoding internal api error bodies. +public struct DefaultHTTPAPIError: HTTPAPIError { + + /// error message. + let message: String + +} diff --git a/Source/Common/Request/Error/DefaultHTTPBodyError.swift b/Source/Common/Request/Error/DefaultHTTPBodyError.swift new file mode 100644 index 0000000..61ac817 --- /dev/null +++ b/Source/Common/Request/Error/DefaultHTTPBodyError.swift @@ -0,0 +1,20 @@ +// +// DefaultHTTPBodyError.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 21/08/2023. +// + +import Foundation + +/// Default type used for decoding http error bodies. +public struct DefaultHTTPBodyError: HTTPBodyError { + + /// response status code. + let statusCode: Int? + /// error message. + let message: String? + /// support identifier. + let supportId: String? + +} diff --git a/Source/Common/Request/Error/HTTPAPIError.swift b/Source/Common/Request/Error/HTTPAPIError.swift new file mode 100644 index 0000000..ac2beb8 --- /dev/null +++ b/Source/Common/Request/Error/HTTPAPIError.swift @@ -0,0 +1,9 @@ +// +// HTTPAPIError.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 20/03/2023. +// + +/// Internal api error body type. +public protocol HTTPAPIError: Error, Decodable { } diff --git a/Source/Common/Request/Error/HTTPBodyError.swift b/Source/Common/Request/Error/HTTPBodyError.swift new file mode 100644 index 0000000..9cd5b7b --- /dev/null +++ b/Source/Common/Request/Error/HTTPBodyError.swift @@ -0,0 +1,11 @@ +// +// HTTPErrorBody.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 21/08/2023. +// + +import Foundation + +/// HTTP body error type. +public protocol HTTPBodyError: Decodable { } diff --git a/Source/Common/Request/Error/HTTPClientError.swift b/Source/Common/Request/Error/HTTPClientError.swift new file mode 100644 index 0000000..efd7a22 --- /dev/null +++ b/Source/Common/Request/Error/HTTPClientError.swift @@ -0,0 +1,13 @@ +// +// HTTPClientError.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 19/02/2023. +// + +/// Client-side (transport) http error. +public enum HTTPClientError: Error { + case response(HTTPStatusCode, HTTPBodyError?) + case serialization(Error) + case transport(Error) +} diff --git a/Source/Common/Request/Error/HTTPError.swift b/Source/Common/Request/Error/HTTPError.swift new file mode 100644 index 0000000..8125ee9 --- /dev/null +++ b/Source/Common/Request/Error/HTTPError.swift @@ -0,0 +1,41 @@ +// +// HTTPError.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 19/02/2023. +// + +import Foundation + +/// Generic http error (client, server or api). +public enum HTTPError: Error { + case client(HTTPClientError) + case server(HTTPServerError) + case api(HTTPAPIError) + + /// Creates `NetworkError` instance. + /// + /// - Parameter response: `HTTPURLResponse` used to get response status code. + public init?(_ response: HTTPURLResponse?, data: Data?, errorType: E.Type) { + if let response = response, + let httpStatusCode = response.status { + // Get Error body from response data if possible. + var httpErrorBody: E? + if let data = data { + httpErrorBody = try? JSONDecoder().decode(errorType.self, from: data) + } + // Get Error from response status code + switch httpStatusCode.responseType { + case .clientError: + self = .client(.response(httpStatusCode, httpErrorBody)) + case .serverError: + self = .server(.response(httpStatusCode, httpErrorBody)) + default: + return nil + } + } else { + return nil + } + } + +} diff --git a/Source/Common/Request/Error/HTTPServerError.swift b/Source/Common/Request/Error/HTTPServerError.swift new file mode 100644 index 0000000..00bbe90 --- /dev/null +++ b/Source/Common/Request/Error/HTTPServerError.swift @@ -0,0 +1,12 @@ +// +// HTTPServerError.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 19/02/2023. +// + +/// Server-side http error. +public enum HTTPServerError: Error { + case response(HTTPStatusCode, HTTPBodyError?) + case generic(Error) +} diff --git a/Source/Common/Request/Interceptor/HTTPRequestAdapter.swift b/Source/Common/Request/Interceptor/HTTPRequestAdapter.swift new file mode 100644 index 0000000..c12f41b --- /dev/null +++ b/Source/Common/Request/Interceptor/HTTPRequestAdapter.swift @@ -0,0 +1,22 @@ +// +// HTTPRequestAdapter.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 27/03/2023. +// + +import Foundation + +/// Adapts outgoing requests. +public protocol HTTPRequestAdapter: AnyObject { + + /// Adapts a given request for a given session. + /// + /// - Parameters: + /// - request: current `URLRequest` + /// - session: current `URLSession` + /// + /// - Returns: `URLRequest` adapted request for a given session. + func adapt(_ request: URLRequest, for session: URLSession) -> URLRequest + +} diff --git a/Source/Common/Request/Interceptor/HTTPRequestInterceptor.swift b/Source/Common/Request/Interceptor/HTTPRequestInterceptor.swift new file mode 100644 index 0000000..e28ef16 --- /dev/null +++ b/Source/Common/Request/Interceptor/HTTPRequestInterceptor.swift @@ -0,0 +1,9 @@ +// +// HTTPRequestInterceptor.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 27/03/2023. +// + +/// Request adapter and retrier. +public typealias HTTPRequestInterceptor = HTTPRequestAdapter & HTTPRequestRetrier diff --git a/Source/Common/Request/Interceptor/HTTPRequestRetrier.swift b/Source/Common/Request/Interceptor/HTTPRequestRetrier.swift new file mode 100644 index 0000000..e4b32d1 --- /dev/null +++ b/Source/Common/Request/Interceptor/HTTPRequestRetrier.swift @@ -0,0 +1,41 @@ +// +// HTTPRequestRetrier.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 27/03/2023. +// + +import Foundation + +/// Retries requests upon failure. +public protocol HTTPRequestRetrier: AnyObject { + + /// Provides maximum retry attempts for a given request and session. + /// + /// - Parameters: + /// - request: current `URLRequest` + /// - session: current `URLSession` + /// + /// - Returns: `Int` maximum retry attempts for given request and session. + func retryMaxAttempts(_ request: URLRequest, for session: URLSession) -> Int + + /// Provides retry policy for a given request and session. + /// + /// - Parameters: + /// - request: current `URLRequest` + /// - session: current `URLSession` + /// + /// - Returns: `NetworkRequestRetryPolicy` retry policy for given request and session. + func retryPolicy(_ request: URLRequest, for session: URLSession) -> HTTPRequestRetryPolicy + + /// Decides if a given request should be reried for a given session and error. + /// + /// - Parameters: + /// - request: current `URLRequest` + /// - session: current `URLSession` + /// - error: encountered `NetworkError` + /// + /// - Returns: `NetworkRequestRetryPolicy` retry policy for given request and session. + func shouldRetry(_ request: URLRequest, for session: URLSession, dueTo error: HTTPError) -> Bool + +} diff --git a/Source/Common/Request/Interceptor/HTTPRequestRetryPolicy.swift b/Source/Common/Request/Interceptor/HTTPRequestRetryPolicy.swift new file mode 100644 index 0000000..fe2f442 --- /dev/null +++ b/Source/Common/Request/Interceptor/HTTPRequestRetryPolicy.swift @@ -0,0 +1,38 @@ +// +// HTTPRequestRetryPolicy.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 27/03/2023. +// + +import Foundation + +// This enum is inspired by Alex Grebenyuk excellent blog https://kean.blog/post/smart-retry +// Here's Alex's twitter: https://twitter.com/a_grebenyuk + +/// Policy for retrying failed requests. +public enum HTTPRequestRetryPolicy { + case immediate + case constant(time: Double) + case exponential(initial: Double, multiplier: Double, maxDelay: Double) + case custom(closure: (Int) -> Double) +} + +public extension HTTPRequestRetryPolicy { + /// Creates time interavel (`Double`) from current policy and given attempt count. + /// + /// - Parameter attempt: current attempt count. + /// + /// - Returns: Time interval `Double` for delay. + func makeTimeInterval(_ attempt: Int) -> Double { + switch self { + case .immediate: return 0.0 + case .constant(let time): return time + case .exponential(let initial, let multiplier, let maxDelay): + // if it's first attempt, simply use initial delay, otherwise calculate delay + let delay = attempt == 1 ? initial : initial * pow(multiplier, Double(attempt - 1)) + return min(maxDelay, delay) + case .custom(let closure): return closure(attempt) + } + } +} diff --git a/Source/Common/Request/Parameters/HTTPMethod.swift b/Source/Common/Request/Parameters/HTTPMethod.swift new file mode 100644 index 0000000..cb23363 --- /dev/null +++ b/Source/Common/Request/Parameters/HTTPMethod.swift @@ -0,0 +1,18 @@ +// +// HTTPMethod.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 20/03/2023. +// + +/// An enumeration of the types of http methods. +public enum HTTPMethod: String { + case get = "GET" + case put = "PUT" + case post = "POST" + case delete = "DELETE" + case head = "HEAD" + case options = "OPTIONS" + case trace = "TRACE" + case connect = "CONNECT" +} diff --git a/Source/Common/Request/Parameters/HTTPScheme.swift b/Source/Common/Request/Parameters/HTTPScheme.swift new file mode 100644 index 0000000..d807277 --- /dev/null +++ b/Source/Common/Request/Parameters/HTTPScheme.swift @@ -0,0 +1,12 @@ +// +// HTTPScheme.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 20/03/2023. +// + +/// An enumeration of the types of http schemes. +public enum HTTPScheme: String { + case http = "http://" + case https = "https://" +} diff --git a/Source/Common/Request/Response/HTTPStatusCode.swift b/Source/Common/Request/Response/HTTPStatusCode.swift new file mode 100644 index 0000000..819121a --- /dev/null +++ b/Source/Common/Request/Response/HTTPStatusCode.swift @@ -0,0 +1,193 @@ +// +// HTTPStatusCode.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 16/02/2023. +// + +import Foundation + +/// This is a list of Hypertext Transfer Protocol (HTTP) response status codes. +/// It includes codes from IETF internet standards, other IETF RFCs, other specifications, and some additional commonly used codes. +/// The first digit of the status code specifies one of five classes of response; an HTTP client must recognise these five classes at a minimum. +public enum HTTPStatusCode: Int, Error { + /// The response class representation of status codes, these get grouped by their first digit. + enum ResponseType { + /// - informational: This class of status code indicates a provisional response, consisting only of the Status-Line and optional headers, and is terminated by an empty line. + case informational + /// - success: This class of status codes indicates the action requested by the client was received, understood, accepted, and processed successfully. + case success + /// - redirection: This class of status code indicates the client must take additional action to complete the request. + case redirection + /// - clientError: This class of status code is intended for situations in which the client seems to have erred. + case clientError + /// - serverError: This class of status code indicates the server failed to fulfill an apparently valid request. + case serverError + /// - undefined: The class of the status code cannot be resolved. + case undefined + } + // + // Informational - 1xx + // + /// - continue: The server has received the request headers and the client should proceed to send the request body. + case `continue` = 100 + /// - switchingProtocols: The requester has asked the server to switch protocols and the server has agreed to do so. + case switchingProtocols = 101 + /// - processing: This code indicates that the server has received and is processing the request, but no response is available yet. + case processing = 102 + // + // Success - 2xx + // + /// - ok: Standard response for successful HTTP requests. + case ok = 200 + /// - created: The request has been fulfilled, resulting in the creation of a new resource. + case created = 201 + /// - accepted: The request has been accepted for processing, but the processing has not been completed. + case accepted = 202 + /// - nonAuthoritativeInformation: The server is a transforming proxy (e.g. a Web accelerator) that received a 200 OK from its origin, but is returning a modified version of the origin's response. + case nonAuthoritativeInformation = 203 + /// - noContent: The server successfully processed the request and is not returning any content. + case noContent = 204 + /// - resetContent: The server successfully processed the request, but is not returning any content. + case resetContent = 205 + /// - partialContent: The server is delivering only part of the resource (byte serving) due to a range header sent by the client. + case partialContent = 206 + /// - multiStatus: The message body that follows is an XML message and can contain a number of separate response codes, depending on how many sub-requests were made. + case multiStatus = 207 + /// - alreadyReported: The members of a DAV binding have already been enumerated in a previous reply to this request, and are not being included again. + case alreadyReported = 208 + /// - IMUsed: The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance. + case IMUsed = 226 + // + // Redirection - 3xx + // + /// - multipleChoices: Indicates multiple options for the resource from which the client may choose + case multipleChoices = 300 + /// - movedPermanently: This and all future requests should be directed to the given URI. + case movedPermanently = 301 + /// - found: The resource was found. + case found = 302 + /// - seeOther: The response to the request can be found under another URI using a GET method. + case seeOther = 303 + /// - notModified: Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match. + case notModified = 304 + /// - useProxy: The requested resource is available only through a proxy, the address for which is provided in the response. + case useProxy = 305 + /// - switchProxy: No longer used. Originally meant "Subsequent requests should use the specified proxy. + case switchProxy = 306 + /// - temporaryRedirect: The request should be repeated with another URI. + case temporaryRedirect = 307 + /// - permenantRedirect: The request and all future requests should be repeated using another URI. + case permenantRedirect = 308 + // + // Client Error - 4xx + // + /// - badRequest: The server cannot or will not process the request due to an apparent client error. + case badRequest = 400 + /// - unauthorized: Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. + case unauthorized = 401 + /// - paymentRequired: The content available on the server requires payment. + case paymentRequired = 402 + /// - forbidden: The request was a valid request, but the server is refusing to respond to it. + case forbidden = 403 + /// - notFound: The requested resource could not be found but may be available in the future. + case notFound = 404 + /// - methodNotAllowed: A request method is not supported for the requested resource. e.g. a GET request on a form which requires data to be presented via POST + case methodNotAllowed = 405 + /// - notAcceptable: The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request. + case notAcceptable = 406 + /// - proxyAuthenticationRequired: The client must first authenticate itself with the proxy. + case proxyAuthenticationRequired = 407 + /// - requestTimeout: The server timed out waiting for the request. + case requestTimeout = 408 + /// - conflict: Indicates that the request could not be processed because of conflict in the request, such as an edit conflict between multiple simultaneous updates. + case conflict = 409 + /// - gone: Indicates that the resource requested is no longer available and will not be available again. + case gone = 410 + /// - lengthRequired: The request did not specify the length of its content, which is required by the requested resource. + case lengthRequired = 411 + /// - preconditionFailed: The server does not meet one of the preconditions that the requester put on the request. + case preconditionFailed = 412 + /// - payloadTooLarge: The request is larger than the server is willing or able to process. + case payloadTooLarge = 413 + /// - URITooLong: The URI provided was too long for the server to process. + case URITooLong = 414 + /// - unsupportedMediaType: The request entity has a media type which the server or resource does not support. + case unsupportedMediaType = 415 + /// - rangeNotSatisfiable: The client has asked for a portion of the file (byte serving), but the server cannot supply that portion. + case rangeNotSatisfiable = 416 + /// - expectationFailed: The server cannot meet the requirements of the Expect request-header field. + case expectationFailed = 417 + /// - teapot: This HTTP status is used as an Easter egg in some websites. + case teapot = 418 + /// - misdirectedRequest: The request was directed at a server that is not able to produce a response. + case misdirectedRequest = 421 + /// - unprocessableEntity: The request was well-formed but was unable to be followed due to semantic errors. + case unprocessableEntity = 422 + /// - locked: The resource that is being accessed is locked. + case locked = 423 + /// - failedDependency: The request failed due to failure of a previous request (e.g., a PROPPATCH). + case failedDependency = 424 + /// - upgradeRequired: The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field. + case upgradeRequired = 426 + /// - preconditionRequired: The origin server requires the request to be conditional. + case preconditionRequired = 428 + /// - tooManyRequests: The user has sent too many requests in a given amount of time. + case tooManyRequests = 429 + /// - requestHeaderFieldsTooLarge: The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large. + case requestHeaderFieldsTooLarge = 431 + /// - noResponse: Used to indicate that the server has returned no information to the client and closed the connection. + case noResponse = 444 + /// - unavailableForLegalReasons: A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource. + case unavailableForLegalReasons = 451 + /// - SSLCertificateError: An expansion of the 400 Bad Request response code, used when the client has provided an invalid client certificate. + case SSLCertificateError = 495 + /// - SSLCertificateRequired: An expansion of the 400 Bad Request response code, used when a client certificate is required but not provided. + case SSLCertificateRequired = 496 + /// - HTTPRequestSentToHTTPSPort: An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for HTTPS requests. + case HTTPRequestSentToHTTPSPort = 497 + /// - clientClosedRequest: Used when the client has closed the request before the server could send a response. + case clientClosedRequest = 499 + // + // Server Error - 5xx + // + /// - internalServerError: A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. + case internalServerError = 500 + /// - notImplemented: The server either does not recognize the request method, or it lacks the ability to fulfill the request. + case notImplemented = 501 + /// - badGateway: The server was acting as a gateway or proxy and received an invalid response from the upstream server. + case badGateway = 502 + /// - serviceUnavailable: The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state. + case serviceUnavailable = 503 + /// - gatewayTimeout: The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. + case gatewayTimeout = 504 + /// - HTTPVersionNotSupported: The server does not support the HTTP protocol version used in the request. + case HTTPVersionNotSupported = 505 + /// - variantAlsoNegotiates: Transparent content negotiation for the request results in a circular reference. + case variantAlsoNegotiates = 506 + /// - insufficientStorage: The server is unable to store the representation needed to complete the request. + case insufficientStorage = 507 + /// - loopDetected: The server detected an infinite loop while processing the request. + case loopDetected = 508 + /// - notExtended: Further extensions to the request are required for the server to fulfill it. + case notExtended = 510 + /// - networkAuthenticationRequired: The client needs to authenticate to gain network access. + case networkAuthenticationRequired = 511 + /// The class (or group) which the status code belongs to. + var responseType: ResponseType { + switch self.rawValue { + case 100..<200: + return .informational + case 200..<300: + return .success + case 300..<400: + return .redirection + case 400..<500: + return .clientError + case 500..<600: + return .serverError + default: + return .undefined + } + } +} diff --git a/Source/Common/Request/Response/HTTPURLResponse+StatusCode.swift b/Source/Common/Request/Response/HTTPURLResponse+StatusCode.swift new file mode 100644 index 0000000..55ed7e8 --- /dev/null +++ b/Source/Common/Request/Response/HTTPURLResponse+StatusCode.swift @@ -0,0 +1,14 @@ +// +// HTTPURLResponse+StatusCode.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 25/03/2023. +// + +import Foundation + +public extension HTTPURLResponse { + var status: HTTPStatusCode? { + return HTTPStatusCode(rawValue: statusCode) + } +} diff --git a/Source/Common/Request/Router/HTTPRequestRouter.swift b/Source/Common/Request/Router/HTTPRequestRouter.swift new file mode 100644 index 0000000..4b86806 --- /dev/null +++ b/Source/Common/Request/Router/HTTPRequestRouter.swift @@ -0,0 +1,69 @@ +// +// HTTPRequestRouter.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 20/03/2023. +// + +import Foundation + +/// Holds request details. +public protocol HTTPRequestRouter { + + /// http scheme of the request. + var scheme: HTTPScheme { get } + /// http method of the request. + var method: HTTPMethod { get } + /// url domain of the request, eg.: `github.com`. + var domain: String { get } + /// url path of the request, eg.: `api/users`. + var path: String { get } + /// http headers of the request. + var headers: [String: String] { get } + /// url query parameters of the request. + var parameters: [String: String]? { get } + /// http body of the request. + var body: [String: Any]? { get } + /// full url of the request (including: domain, path and query parameters). + var url: URL? { get } + + /// Creates a `URLRequest` object using router properties. + /// + /// - Returns: `URLRequest` created using router properties. + func asURLRequest() -> URLRequest + +} + +public extension HTTPRequestRouter { + + /// full url of the request (including: domain and path). + var url: URL? { + let urlString = scheme.rawValue + domain + "/" + path + return URL(string: urlString) + } + + /// Creates `URLRequest` object using router properties. + /// + /// - Returns: `URLRequest` created using router properties. + func asURLRequest() -> URLRequest { + var url: URL? = self.url + // Add qurey parameters + if let parameters = parameters { + var urlComponents = URLComponents(url: url!, resolvingAgainstBaseURL: false) + urlComponents?.queryItems = parameters.map { URLQueryItem(name: $0, value: $1) } + url = urlComponents?.url + } + var request = URLRequest(url: url!) + // Apply HTTP method + request.httpMethod = method.rawValue + // Apply HTTP body (if method is not GET) + if let body = body, + method != .get { + request.httpBody = try? JSONSerialization.data(withJSONObject: body) + } + // Apply HTTP headers + request.allHTTPHeaderFields = headers + return request + } + +} diff --git a/Source/REST/Extensions/Completable+Retry.swift b/Source/REST/Extensions/Completable+Retry.swift index 4d6e98e..bc76242 100644 --- a/Source/REST/Extensions/Completable+Retry.swift +++ b/Source/REST/Extensions/Completable+Retry.swift @@ -8,7 +8,6 @@ import Foundation import RxSwift import RxCocoa -import CoreHTTP // This extension is inspired by Alex Grebenyuk's excellent blog https://kean.blog/post/smart-retry // Here's Alex's twitter: https://twitter.com/a_grebenyuk diff --git a/Source/RxNetworkKit.swift b/Source/RxNetworkKit.swift deleted file mode 100644 index abc3b9d..0000000 --- a/Source/RxNetworkKit.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// RxNetworkKit.swift -// RxNetworkKit -// -// Created by Loay Ashraf on 11/10/2023. -// - -@_exported import CoreHTTP