From f851a3c08a8cc87a2b75e336ab5464855b812aea Mon Sep 17 00:00:00 2001 From: Egzon Pllana <> Date: Sun, 25 Aug 2024 10:42:59 +0200 Subject: [PATCH] Push project to GitHub. --- NetworkingLayerSwift6.drawio | 67 +++ .../project.pbxproj | 493 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/swiftpm/Package.resolved | 15 + .../IDEFindNavigatorScopes.plist | 5 + .../UserInterfaceState.xcuserstate | Bin 0 -> 168456 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 + .../xcschemes/xcschememanagement.plist | 14 + .../Architecture/Data/Entities/PostDTO.swift | 28 + .../Architecture/Data/PostsRepository.swift | 72 +++ .../Protocols/PostsRepositoryProtocol.swift | 26 + .../PostsRepositoryUseCaseProtocol.swift | 26 + .../UseCases/PostsRepositoryUseCase.swift | 34 ++ .../Infrastructure/Networking/APIClient.swift | 173 ++++++ .../Networking/APIEndpoints.swift | 84 +++ .../Networking/Data/MultipartFormData.swift | 53 ++ .../Data/UploadProgressDelegate.swift | 36 ++ .../Networking/Domain/APIClientError.swift | 30 ++ .../Networking/Domain/APIEndpoint.swift | 13 + .../Networking/Domain/APIVersion.swift | 13 + .../Networking/Domain/HTTPMethod.swift | 23 + .../Networking/Domain/ImageMimeType.swift | 21 + .../Protocols/APIClientProtocol.swift | 83 +++ .../Protocols/APIEndpointProtocol.swift | 76 +++ .../NetworkingLayerSwift6App.swift | 35 ++ .../Presentation/Home/HomeView.swift | 60 +++ .../Presentation/Home/HomeViewModel.swift | 59 +++ .../Home/HomeViewModelProtocol.swift | 50 ++ .../Preview Assets.xcassets/Contents.json | 6 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ .../Resources/Assets.xcassets/Contents.json | 6 + NetworkingLayerSwift6/Resources/image-png.png | Bin 0 -> 60440 bytes .../APIClientTests.swift | 265 ++++++++++ .../APIEndpointTests.swift | 200 +++++++ README.md | 4 +- 36 files changed, 2127 insertions(+), 2 deletions(-) create mode 100644 NetworkingLayerSwift6.drawio create mode 100644 NetworkingLayerSwift6.xcodeproj/project.pbxproj create mode 100644 NetworkingLayerSwift6.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcuserdata/egzonpllana.xcuserdatad/IDEFindNavigatorScopes.plist create mode 100644 NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcuserdata/egzonpllana.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 NetworkingLayerSwift6.xcodeproj/xcuserdata/egzonpllana.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 NetworkingLayerSwift6.xcodeproj/xcuserdata/egzonpllana.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 NetworkingLayerSwift6/Architecture/Data/Entities/PostDTO.swift create mode 100644 NetworkingLayerSwift6/Architecture/Data/PostsRepository.swift create mode 100644 NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryProtocol.swift create mode 100644 NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryUseCaseProtocol.swift create mode 100644 NetworkingLayerSwift6/Architecture/Domain/UseCases/PostsRepositoryUseCase.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIClient.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIEndpoints.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/MultipartFormData.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/UploadProgressDelegate.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIClientError.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIEndpoint.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIVersion.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/HTTPMethod.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/ImageMimeType.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIClientProtocol.swift create mode 100644 NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIEndpointProtocol.swift create mode 100644 NetworkingLayerSwift6/Architecture/NetworkingLayerSwift6App.swift create mode 100644 NetworkingLayerSwift6/Architecture/Presentation/Home/HomeView.swift create mode 100644 NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModel.swift create mode 100644 NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModelProtocol.swift create mode 100644 NetworkingLayerSwift6/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 NetworkingLayerSwift6/Resources/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 NetworkingLayerSwift6/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 NetworkingLayerSwift6/Resources/Assets.xcassets/Contents.json create mode 100644 NetworkingLayerSwift6/Resources/image-png.png create mode 100644 NetworkingLayerSwift6Tests/APIClientTests.swift create mode 100644 NetworkingLayerSwift6Tests/APIEndpointTests.swift diff --git a/NetworkingLayerSwift6.drawio b/NetworkingLayerSwift6.drawio new file mode 100644 index 0000000..2cbd740 --- /dev/null +++ b/NetworkingLayerSwift6.drawio @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NetworkingLayerSwift6.xcodeproj/project.pbxproj b/NetworkingLayerSwift6.xcodeproj/project.pbxproj new file mode 100644 index 0000000..767ae80 --- /dev/null +++ b/NetworkingLayerSwift6.xcodeproj/project.pbxproj @@ -0,0 +1,493 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 521B5E8E2C7A140E0055AFE7 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 521B5E8D2C7A140E0055AFE7 /* Alamofire */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 5240D4D42C72A90E000F58CD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 52EB7E402C70B90400069B79 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 52EB7E472C70B90400069B79; + remoteInfo = NetworkingLayerSwift6; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 5240D4D02C72A90E000F58CD /* NetworkingLayerSwift6Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NetworkingLayerSwift6Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 52EB7E482C70B90400069B79 /* NetworkingLayerSwift6.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetworkingLayerSwift6.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 5240D4D12C72A90E000F58CD /* NetworkingLayerSwift6Tests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = NetworkingLayerSwift6Tests; + sourceTree = ""; + }; + 52EB7E4A2C70B90400069B79 /* NetworkingLayerSwift6 */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = NetworkingLayerSwift6; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5240D4CD2C72A90E000F58CD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52EB7E452C70B90400069B79 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 521B5E8E2C7A140E0055AFE7 /* Alamofire in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 52EB7E3F2C70B90400069B79 = { + isa = PBXGroup; + children = ( + 52EB7E4A2C70B90400069B79 /* NetworkingLayerSwift6 */, + 5240D4D12C72A90E000F58CD /* NetworkingLayerSwift6Tests */, + 52EB7E492C70B90400069B79 /* Products */, + ); + sourceTree = ""; + }; + 52EB7E492C70B90400069B79 /* Products */ = { + isa = PBXGroup; + children = ( + 52EB7E482C70B90400069B79 /* NetworkingLayerSwift6.app */, + 5240D4D02C72A90E000F58CD /* NetworkingLayerSwift6Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5240D4CF2C72A90E000F58CD /* NetworkingLayerSwift6Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5240D4D82C72A90E000F58CD /* Build configuration list for PBXNativeTarget "NetworkingLayerSwift6Tests" */; + buildPhases = ( + 5240D4CC2C72A90E000F58CD /* Sources */, + 5240D4CD2C72A90E000F58CD /* Frameworks */, + 5240D4CE2C72A90E000F58CD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5240D4D52C72A90E000F58CD /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 5240D4D12C72A90E000F58CD /* NetworkingLayerSwift6Tests */, + ); + name = NetworkingLayerSwift6Tests; + packageProductDependencies = ( + ); + productName = NetworkingLayerSwift6Tests; + productReference = 5240D4D02C72A90E000F58CD /* NetworkingLayerSwift6Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 52EB7E472C70B90400069B79 /* NetworkingLayerSwift6 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 52EB7E562C70B90500069B79 /* Build configuration list for PBXNativeTarget "NetworkingLayerSwift6" */; + buildPhases = ( + 52EB7E442C70B90400069B79 /* Sources */, + 52EB7E452C70B90400069B79 /* Frameworks */, + 52EB7E462C70B90400069B79 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 52EB7E4A2C70B90400069B79 /* NetworkingLayerSwift6 */, + ); + name = NetworkingLayerSwift6; + packageProductDependencies = ( + 521B5E8D2C7A140E0055AFE7 /* Alamofire */, + ); + productName = NetworkingLayerSwift6; + productReference = 52EB7E482C70B90400069B79 /* NetworkingLayerSwift6.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 52EB7E402C70B90400069B79 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1610; + LastUpgradeCheck = 1610; + TargetAttributes = { + 5240D4CF2C72A90E000F58CD = { + CreatedOnToolsVersion = 16.1; + TestTargetID = 52EB7E472C70B90400069B79; + }; + 52EB7E472C70B90400069B79 = { + CreatedOnToolsVersion = 16.1; + }; + }; + }; + buildConfigurationList = 52EB7E432C70B90400069B79 /* Build configuration list for PBXProject "NetworkingLayerSwift6" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 52EB7E3F2C70B90400069B79; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 521B5E8C2C7A140E0055AFE7 /* XCRemoteSwiftPackageReference "Alamofire" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 52EB7E492C70B90400069B79 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 52EB7E472C70B90400069B79 /* NetworkingLayerSwift6 */, + 5240D4CF2C72A90E000F58CD /* NetworkingLayerSwift6Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5240D4CE2C72A90E000F58CD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52EB7E462C70B90400069B79 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5240D4CC2C72A90E000F58CD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52EB7E442C70B90400069B79 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 5240D4D52C72A90E000F58CD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 52EB7E472C70B90400069B79 /* NetworkingLayerSwift6 */; + targetProxy = 5240D4D42C72A90E000F58CD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 5240D4D62C72A90E000F58CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = YUQDS8QJFD; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = egzonpllana.NetworkingLayerSwift6Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NetworkingLayerSwift6.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NetworkingLayerSwift6"; + XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + 5240D4D72C72A90E000F58CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = YUQDS8QJFD; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = egzonpllana.NetworkingLayerSwift6Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NetworkingLayerSwift6.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/NetworkingLayerSwift6"; + XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; + 52EB7E542C70B90500069B79 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 52EB7E552C70B90500069B79 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 52EB7E572C70B90500069B79 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"NetworkingLayerSwift6/Preview Content\""; + DEVELOPMENT_TEAM = YUQDS8QJFD; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = egzonpllana.NetworkingLayerSwift6; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 52EB7E582C70B90500069B79 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"NetworkingLayerSwift6/Preview Content\""; + DEVELOPMENT_TEAM = YUQDS8QJFD; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = egzonpllana.NetworkingLayerSwift6; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES; + SWIFT_VERSION = 6.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5240D4D82C72A90E000F58CD /* Build configuration list for PBXNativeTarget "NetworkingLayerSwift6Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5240D4D62C72A90E000F58CD /* Debug */, + 5240D4D72C72A90E000F58CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 52EB7E432C70B90400069B79 /* Build configuration list for PBXProject "NetworkingLayerSwift6" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 52EB7E542C70B90500069B79 /* Debug */, + 52EB7E552C70B90500069B79 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 52EB7E562C70B90500069B79 /* Build configuration list for PBXNativeTarget "NetworkingLayerSwift6" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 52EB7E572C70B90500069B79 /* Debug */, + 52EB7E582C70B90500069B79 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 521B5E8C2C7A140E0055AFE7 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.9.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 521B5E8D2C7A140E0055AFE7 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 521B5E8C2C7A140E0055AFE7 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 52EB7E402C70B90400069B79 /* Project object */; +} diff --git a/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..baf1060 --- /dev/null +++ b/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "e8f130fe30ac6cdc940ef06ee1e8535e9f46ffee6aeead1722b9525562f6ce08", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", + "version" : "5.9.1" + } + } + ], + "version" : 3 +} diff --git a/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcuserdata/egzonpllana.xcuserdatad/IDEFindNavigatorScopes.plist b/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcuserdata/egzonpllana.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcuserdata/egzonpllana.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcuserdata/egzonpllana.xcuserdatad/UserInterfaceState.xcuserstate b/NetworkingLayerSwift6.xcodeproj/project.xcworkspace/xcuserdata/egzonpllana.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..34804bfd5f9ca05312d59dddd71baf293aae6336 GIT binary patch literal 168456 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 z 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 z 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

GbsBZKm7CVbFZ9n7N9OXOQLMOM-IpBQE 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 SzkfEX{l9 + + diff --git a/NetworkingLayerSwift6.xcodeproj/xcuserdata/egzonpllana.xcuserdatad/xcschemes/xcschememanagement.plist b/NetworkingLayerSwift6.xcodeproj/xcuserdata/egzonpllana.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..24fde04 --- /dev/null +++ b/NetworkingLayerSwift6.xcodeproj/xcuserdata/egzonpllana.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + NetworkingLayerSwift6.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/NetworkingLayerSwift6/Architecture/Data/Entities/PostDTO.swift b/NetworkingLayerSwift6/Architecture/Data/Entities/PostDTO.swift new file mode 100644 index 0000000..a526e91 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Data/Entities/PostDTO.swift @@ -0,0 +1,28 @@ +// +// PostDTO.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// A struct representing the data transfer object for a post. +struct PostDTO: Codable { + let userId: Int + let title: String + let body: String +} + +extension PostDTO { + /// Converts the `PostDTO` data to JSON format for use in a request body. + /// + /// - Returns: A `Data` object representing the post data in JSON format, or `nil` if the conversion fails. + func toJSONData() -> Data? { + let jsonObject: [String: Any] = [ + "userId": userId, + "title": title, + "body": body + ] + return try? JSONSerialization.data(withJSONObject: jsonObject, options: []) + } +} diff --git a/NetworkingLayerSwift6/Architecture/Data/PostsRepository.swift b/NetworkingLayerSwift6/Architecture/Data/PostsRepository.swift new file mode 100644 index 0000000..fbb2355 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Data/PostsRepository.swift @@ -0,0 +1,72 @@ +// +// PostsRepository.swift +// +// Created by Egzon Pllana. +// + +import UIKit + +enum PostConstants { + static let defaultUserId = 1 + static let defaultTitle = "Title here" + static let defaultBody = "Body here" + static let smallImageName = "image-png.png" + static let imageName = "image-name" +} + +/// Concrete implementation of `PostsRepositoryProtocol` for managing posts and image uploads. +final class PostsRepository: PostsRepositoryProtocol { + private let apiClient: any APIClientProtocol + private typealias apiEndpoint = APIEndpoint + + init(apiClient: any APIClientProtocol = APIClient()) { + self.apiClient = apiClient + } + + func getPosts() async throws -> [PostDTO] { + let fetchedPosts: [PostDTO] = try await apiClient.request(apiEndpoint.getPosts) + return fetchedPosts + } + + func createPost() async throws { + let newPost = PostDTO( + userId: PostConstants.defaultUserId, + title: PostConstants.defaultTitle, + body: PostConstants.defaultBody + ) + do { + try await apiClient.requestVoid(apiEndpoint.createPost(newPost)) + } catch { + log("Error: \(error)") + throw error + } + } + + func uploadImage( + data: Data, + fileName: String, + mimeType: String, + progressDelegate: (any UploadProgressDelegateProtocol)? = nil + ) async throws { + let endPoint = apiEndpoint.uploadImage( + data: data, + fileName: fileName, + mimeType: ImageMimeType(rawValue: mimeType) ?? .png + ) + do { + try await apiClient.requestWithProgress( + endPoint, + progressDelegate: progressDelegate + ) + } catch { + log("Error uploading image: \(error)") + throw error + } + } + + private func log(_ string: String) { + #if DEBUG + print(string) + #endif + } +} diff --git a/NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryProtocol.swift b/NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryProtocol.swift new file mode 100644 index 0000000..0140140 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryProtocol.swift @@ -0,0 +1,26 @@ +// +// PostsRepositoryProtocol.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// Protocol defining the operations for managing posts and uploading images. +protocol PostsRepositoryProtocol: Sendable { + /// Fetches posts from the API. + /// - Returns: An array of `PostDTO` objects. + /// - Throws: An error if the request fails. + func getPosts() async throws -> [PostDTO] + + /// Creates a new post with default values. + /// - Throws: An error if the request fails. + func createPost() async throws + + func uploadImage( + data: Data, + fileName: String, + mimeType: String, + progressDelegate: (any UploadProgressDelegateProtocol)? + ) async throws +} diff --git a/NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryUseCaseProtocol.swift b/NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryUseCaseProtocol.swift new file mode 100644 index 0000000..4639069 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Domain/Protocols/PostsRepositoryUseCaseProtocol.swift @@ -0,0 +1,26 @@ +// +// PostsRepositoryUseCaseProtocol.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// Protocol defining the use case for managing post-related operations. +protocol PostsRepositoryUseCaseProtocol: Sendable { + /// Retrieves posts using the `PostsRepository`. + /// - Returns: An array of `PostDTO` objects. + /// - Throws: An error if the service request fails. + func getPosts() async throws -> [PostDTO] + + /// Creates a new post using the `PostsRepository`. + /// - Throws: An error if the service request fails. + func createPost() async throws + + func uploadImage( + data: Data, + fileName: String, + mimeType: String, + progressDelegate: (any UploadProgressDelegateProtocol)? + ) async throws +} diff --git a/NetworkingLayerSwift6/Architecture/Domain/UseCases/PostsRepositoryUseCase.swift b/NetworkingLayerSwift6/Architecture/Domain/UseCases/PostsRepositoryUseCase.swift new file mode 100644 index 0000000..3f371ba --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Domain/UseCases/PostsRepositoryUseCase.swift @@ -0,0 +1,34 @@ +// +// PostsRepositoryUseCase.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// Concrete implementation of `PostsRepositoryUseCaseProtocol` for managing post-related operations. +final class PostsRepositoryUseCase: PostsRepositoryUseCaseProtocol { + + private let postsRepository: any PostsRepositoryProtocol + + init(postsRepository: any PostsRepositoryProtocol = PostsRepository()) { + self.postsRepository = postsRepository + } + + func getPosts() async throws -> [PostDTO] { + return try await postsRepository.getPosts() + } + + func createPost() async throws { + try await postsRepository.createPost() + } + + func uploadImage(data: Data, fileName: String, mimeType: String, progressDelegate: (any UploadProgressDelegateProtocol)?) async throws { + try await postsRepository.uploadImage( + data: data, + fileName: fileName, + mimeType: mimeType, + progressDelegate: progressDelegate + ) + } +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIClient.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIClient.swift new file mode 100644 index 0000000..ff52694 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIClient.swift @@ -0,0 +1,173 @@ +import Foundation +import Alamofire + +private extension String { + static let tokenWithSpace = "Token " + static let authorization = "Authorization" +} + +final class APIClient: APIClientProtocol { + // MARK: - Properties - + private let token: String? + private let session: URLSession + + // MARK: - Initialization - + init(token: String? = nil, session: URLSession = .shared) { + self.token = token + self.session = session + } + + // MARK: - Methods - + func request( + _ endpoint: any APIEndpointProtocol, + decoder: JSONDecoder + ) async throws -> T { + guard let request = endpoint.urlRequest else { + throw APIClientError.invalidURL + } + + // Perform the network request and decode the data + let data = try await performRequest(request) + do { + return try decoder.decode(T.self, from: data) + } catch { + // Handle decoding errors + throw APIClientError.decodingFailed(error) + } + } + + func requestVoid( + _ endpoint: any APIEndpointProtocol + ) async throws { + guard let request = endpoint.urlRequest else { + throw APIClientError.invalidURL + } + + // Perform the network request + try await performRequest(request) + } + + @discardableResult + func requestWithProgress( + _ endpoint: any APIEndpointProtocol, + progressDelegate: ( + any UploadProgressDelegateProtocol + )? + ) async throws -> Data? { + guard let request = endpoint.urlRequest else { + throw APIClientError.urlRequestIsEmpty + } + + do { + let data = try await performRequest(request, progressDelegate: progressDelegate) + return data + } catch { + throw error + } + } +} + +// Additional method just in case you want to work with Alamofire framework. +extension APIClient { + + func requestWithAlamofire( + _ endpoint: any APIEndpointProtocol, + decoder: JSONDecoder + ) async throws -> T { + guard let urlRequest = endpoint.urlRequest else { + throw APIClientError.invalidURL + } + + // Create a request using Alamofire + let request = AF.request(urlRequest) + + do { + // Await the Alamofire request + let data = try await withCheckedThrowingContinuation { continuation in + request.responseData { response in + switch response.result { + case .success(let data): + continuation.resume(returning: data) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + + // Decode the response data + do { + return try decoder.decode(T.self, from: data) + } catch { + throw APIClientError.decodingFailed(error) + } + } catch { + // Handle Alamofire errors + if let urlError = error as? URLError { + throw APIClientError.networkError(urlError) + } else { + throw APIClientError.requestFailed(error) + } + } + } +} + +// MARK: - Private extensions - +private extension APIClient { + + @discardableResult + private func performRequest( + _ request: URLRequest, + progressDelegate: (any UploadProgressDelegateProtocol)? = nil + ) async throws -> Data { + // Inject token if available + if let token = token { + var mutableRequest = request + mutableRequest.addValue(.tokenWithSpace + String(token), forHTTPHeaderField: .authorization) + } + + // Configure session + let session: URLSession + if let progressDelegate { + session = URLSession(configuration: .default, delegate: progressDelegate, delegateQueue: nil) + } else { + session = self.session + } + + do { + // Perform the network request + let (data, response) = try await session.data(for: request) + + // Ensure the response is an HTTP URL response + guard let httpResponse = response as? HTTPURLResponse else { + throw APIClientError.invalidResponse(data) + } + + // Log the HTTP response + log("Received HTTP response: \(httpResponse)") + + // Validate HTTP status code + guard (200...299).contains(httpResponse.statusCode) else { + throw APIClientError.statusCode(httpResponse.statusCode) + } + + return data + } catch { + // Handle specific errors + if let urlError = error as? URLError { + throw APIClientError.networkError(urlError) + } else { + throw APIClientError.requestFailed(error) + } + } + } +} + +// MARK: - Log extension - +private extension APIClient { + + private func log(_ string: String) { + #if DEBUG + print(string) + #endif + } +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIEndpoints.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIEndpoints.swift new file mode 100644 index 0000000..daaa98f --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/APIEndpoints.swift @@ -0,0 +1,84 @@ +// +// APIEndpoint.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +private enum Constants { + static let baseURL = "https://jsonplaceholder.typicode.com" + static let uploadPath = "upload" + static let postPath = "posts" + + static let contentTypeHeader = "Content-Type" + static let multipartFormDataContentType = "multipart/form-data" +} + +/// Extension to conform to `APIEndpointProtocol`. +extension APIEndpoint: APIEndpointProtocol { + /// API version used by endpoints. + var apiVersion: APIVersion { + .v1 + } + + /// Endpoint base URL. + var baseURL: String { + return Constants.baseURL + } + + /// Endpoint HTTP method. + var method: HTTPMethod { + switch self { + case .getPosts: + return .get + case .createPost, .uploadImage: + return .post + } + } + + /// Endpoint path. + var path: String { + switch self { + case .getPosts, .createPost: + return Constants.postPath + case .uploadImage: + return Constants.uploadPath + } + } + + /// Request headers. + var headers: [String: String] { + switch self { + case .uploadImage: + return [Constants.contentTypeHeader: Constants.multipartFormDataContentType] + case .getPosts, .createPost: + return [:] + } + } + + /// Request URL parameters. + var urlParams: [String: any CustomStringConvertible] { + return [:] + } + + /// Request body data. + var body: Data? { + switch self { + case .createPost(let postDTO): + return postDTO.toJSONData() + case .uploadImage(let data, let fileName, let mimeType): + let boundary = UUID().uuidString + let multipartData = MultipartFormData( + boundary: boundary, + fileData: data, + fileName: fileName, + mimeType: mimeType.asString, + parameters: [:] + ) + return multipartData.asHttpBodyData + case .getPosts: + return nil + } + } +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/MultipartFormData.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/MultipartFormData.swift new file mode 100644 index 0000000..d195611 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/MultipartFormData.swift @@ -0,0 +1,53 @@ +// +// MultipartFormData.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// A model representing multipart form data configuration. +struct MultipartFormData { + /// Boundary string used to separate parts. + let boundary: String + + /// Data of the file to upload. + let fileData: Data + + /// Name of the file. + let fileName: String + + /// MIME type of the file. + let mimeType: String + + /// Parameters to include in the multipart form data. + let parameters: [String: String] + +} + +extension MultipartFormData { + + /// Creates multipart form data body. + var asHttpBodyData: Data { + var body = Data() + + // Add parameters + for (key, value) in parameters { + body.append("--\(boundary)\r\n".data(using: .utf8)!) + body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!) + body.append("\(value)\r\n".data(using: .utf8)!) + } + + // Add file data + body.append("--\(boundary)\r\n".data(using: .utf8)!) + body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!) + body.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!) + body.append(fileData) + body.append("\r\n".data(using: .utf8)!) + + // End boundary + body.append("--\(boundary)--\r\n".data(using: .utf8)!) + + return body + } +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/UploadProgressDelegate.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/UploadProgressDelegate.swift new file mode 100644 index 0000000..4719d8e --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Data/UploadProgressDelegate.swift @@ -0,0 +1,36 @@ +// +// UploadProgressDelegate.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// A protocol to handle upload progress with URLSession tasks. +protocol UploadProgressDelegateProtocol: URLSessionTaskDelegate, Sendable { } + +/// A delegate class for handling upload progress. +final class UploadProgressDelegate: NSObject, UploadProgressDelegateProtocol { + + /// A closure to handle the progress updates. This closure will be called on the main thread. + private let progressHandler: (@Sendable (Double) -> Void)? + + /// Initializes the delegate with a progress handler. + /// + /// - Parameter progressHandler: A closure that will be called to handle progress updates. + init(progressHandler: (@Sendable (Double) -> Void)?) { + self.progressHandler = progressHandler + } + + /// This method is called by the URLSession whenever data is sent during an upload task. + nonisolated func urlSession( + _ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64 + ) { + let progress = Double(totalBytesSent) / Double(totalBytesExpectedToSend) + progressHandler?(progress) + } +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIClientError.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIClientError.swift new file mode 100644 index 0000000..f0c0be9 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIClientError.swift @@ -0,0 +1,30 @@ +// +// APIClientError.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// An enumeration representing errors that can occur during API client operations. +/// +/// This enum encapsulates various errors that may arise when performing network requests, handling responses, or decoding data. Each case provides information about the nature of the error. +/// +/// - `invalidURL`: The URL is invalid or cannot be constructed. +/// - `invalidResponse(_ data: Data)`: The response is invalid, and the associated data provides additional context. +/// - `requestFailed(_ error: any Error)`: The request failed due to an underlying error, which is provided. +/// - `decodingFailed(_ error: any Error)`: Decoding the response data failed, with the associated error providing details. +/// - `notExpectedHttpResponseCode(code: Int)`: The HTTP response code was not as expected, with the actual code provided. +/// - `urlRequestIsEmpty`: The URLRequest could not be created. +/// - `statusCode(Int)`: The status code from the server response is provided. +/// - `networkError(any Error)`: A network error occurred, with the underlying error provided. +enum APIClientError: Error { + case invalidURL + case invalidResponse(_ data: Data) + case requestFailed(_ error: any Error) + case decodingFailed(_ error: any Error) + case notExpectedHttpResponseCode(code: Int) + case urlRequestIsEmpty + case statusCode(Int) + case networkError(any Error) +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIEndpoint.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIEndpoint.swift new file mode 100644 index 0000000..8ddaffd --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIEndpoint.swift @@ -0,0 +1,13 @@ +// +// APIEndpoint.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +enum APIEndpoint { + case getPosts + case createPost(PostDTO) + case uploadImage(data: Data, fileName: String, mimeType: ImageMimeType) +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIVersion.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIVersion.swift new file mode 100644 index 0000000..9d9373b --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/APIVersion.swift @@ -0,0 +1,13 @@ +// +// APIVersion.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// Enum defining different versions of the API. +enum APIVersion: String { + /// Version 1 of the API. + case v1 = "/api/v1/" +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/HTTPMethod.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/HTTPMethod.swift new file mode 100644 index 0000000..a7ae014 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/HTTPMethod.swift @@ -0,0 +1,23 @@ +// +// HTTPMethod.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// Inspired by: +/// https://tools.ietf.org/html/rfc7231#section-4.3 + +/// Enum defining HTTP methods. +enum HTTPMethod: String { + case options = "OPTIONS" + case get = "GET" + case head = "HEAD" + case post = "POST" + case put = "PUT" + case patch = "PATCH" + case delete = "DELETE" + case trace = "TRACE" + case connect = "CONNECT" +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/ImageMimeType.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/ImageMimeType.swift new file mode 100644 index 0000000..6f7c3b0 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Domain/ImageMimeType.swift @@ -0,0 +1,21 @@ +// +// ImageMimeType.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +enum ImageMimeType: String { + case jpeg = "image/jpeg" + case png = "image/png" + case gif = "image/gif" + case bmp = "image/bmp" + case tiff = "image/tiff" + case svg = "image/svg+xml" + + /// Returns the corresponding MIME type string for the image format. + var asString: String { + return self.rawValue + } +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIClientProtocol.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIClientProtocol.swift new file mode 100644 index 0000000..e0a8e5f --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIClientProtocol.swift @@ -0,0 +1,83 @@ +import Foundation + +/// A protocol defining the methods for making API requests. +protocol APIClientProtocol: Sendable { + /// Sends a request to the specified endpoint and decodes the response into a given type. + /// + /// - Parameters: + /// - endpoint: The endpoint to request. + /// - decoder: The `JSONDecoder` to use for decoding the response. + /// - Returns: The decoded response of type `T`. + /// - Throws: An error if the request fails or if decoding fails. + /// - Note: The type `T` must conform to `Codable` and `Sendable`. + /// + func request( + _ endpoint: any APIEndpointProtocol, + decoder: JSONDecoder + ) async throws -> T + + /// Sends a request that does not return the response body. + /// + /// - Parameters: + /// - endpoint: The endpoint to request. + /// - decoder: The `JSONDecoder` to use for decoding the response. + /// - Returns: The decoded response of type `T`. + /// - Throws: An error if the request fails or if decoding fails. + /// - This method can be used for various HTTP methods that we are not interested in the response/return value but only if it succeed or fails, such as `POST`, `DELETE`, and `PATCH` and more. + /// + func requestVoid( + _ endpoint: any APIEndpointProtocol + ) async throws + + /// Sends a request to the specified endpoint and returns the raw data with the upload progress. + /// + /// - Parameters: + /// - endpoint: The endpoint to request. + /// - progressDelegate: An optional delegate for tracking upload progress. + /// - Returns: The raw `Data` received from the request, or `nil` if no data is received. + /// - Throws: An error if the request fails. + /// + @discardableResult + func requestWithProgress( + _ endpoint: any APIEndpointProtocol, + progressDelegate: (any UploadProgressDelegateProtocol)? + ) async throws -> Data? + + /// Sends a request to the specified endpoint and decodes the response into a given type using Alamofire framework. + /// + /// - Parameters: + /// - endpoint: The endpoint to request. + /// - decoder: The `JSONDecoder` to use for decoding the response. + /// - Returns: The decoded response of type `T`. + /// - Throws: An error if the request fails or if decoding fails. + /// - Note: The type `T` must conform to `Decodable` and `Sendable`. + /// + func requestWithAlamofire( + _ endpoint: any APIEndpointProtocol, + decoder: JSONDecoder + ) async throws -> T +} + +extension APIClientProtocol { + // With default decoder parameter: JSONDecoder() + @discardableResult + func request(_ endpoint: any APIEndpointProtocol) async throws -> T { + try await request(endpoint, decoder: JSONDecoder()) + } + + // With default decoder parameter: JSONDecoder() + @discardableResult + func requestWithAlamofire( + _ endpoint: any APIEndpointProtocol + ) async throws -> T { + try await requestWithAlamofire(endpoint, decoder: JSONDecoder()) + } + + // With default delegate parameter: nil + @discardableResult + func requestData( + _ endpoint: any APIEndpointProtocol + ) async throws -> Data? { + try await requestWithProgress(endpoint, progressDelegate: nil) + } +} diff --git a/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIEndpointProtocol.swift b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIEndpointProtocol.swift new file mode 100644 index 0000000..7c8e3d4 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Infrastructure/Networking/Protocols/APIEndpointProtocol.swift @@ -0,0 +1,76 @@ +// +// APIEndpointProtocol.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// A protocol defining the requirements for an API endpoint. +/// +/// This protocol specifies the necessary components to construct a URL request for an API endpoint. It includes details such as the HTTP method, path, base URL, headers, URL parameters, body, and API version. Conforming types must provide these components to enable API requests. +/// +/// - `method`: The HTTP method to be used for the request (e.g., GET, POST). +/// - `path`: The specific path for the endpoint relative to the base URL. +/// - `baseURL`: The base URL for the API. +/// - `headers`: A dictionary of HTTP headers to include in the request. +/// - `urlParams`: A dictionary of query parameters to include in the URL. The values must conform to `CustomStringConvertible`. +/// - `body`: The body of the request, if any, as `Data`. +/// - `urlRequest`: A computed `URLRequest` based on the provided components. Returns `nil` if the URL cannot be constructed. +/// - `apiVersion`: The version of the API being used. +/// +protocol APIEndpointProtocol { + /// HTTP method used by the endpoint. + var method: HTTPMethod { get } + + /// Path for the endpoint. + var path: String { get } + + /// Base URL for the API. + var baseURL: String { get } + + /// Headers for the request. + var headers: [String: String] { get } + + /// URL parameters for the request. + var urlParams: [String: any CustomStringConvertible] { get } + + /// Body data for the request. + var body: Data? { get } + + /// URLRequest representation of the endpoint. + var urlRequest: URLRequest? { get } + + /// API version used by the endpoint. + var apiVersion: APIVersion { get } +} + +extension APIEndpointProtocol { + + /// A computed property that constructs and returns a `URLRequest` for the endpoint. + /// + /// This property assembles a `URLRequest` by combining the base URL, API version, and path. It adds any query parameters and sets the HTTP method, headers, and body as specified by the endpoint. + /// + /// - Returns: A `URLRequest` if the URL components can be successfully created, otherwise `nil`. + /// + /// ### Example + /// ```swift + /// let request = endpoint.urlRequest + /// // Use the request with URLSession or any networking library. + /// ``` + var urlRequest: URLRequest? { + var components = URLComponents(string: baseURL + apiVersion.rawValue + path) + components?.queryItems = urlParams.map { key, value in + URLQueryItem(name: key, value: String(describing: value)) + } + + guard let url = components?.url else { return nil } + + var request = URLRequest(url: url) + request.httpMethod = method.rawValue + request.allHTTPHeaderFields = headers + request.httpBody = body + + return request + } +} diff --git a/NetworkingLayerSwift6/Architecture/NetworkingLayerSwift6App.swift b/NetworkingLayerSwift6/Architecture/NetworkingLayerSwift6App.swift new file mode 100644 index 0000000..5d9d0d8 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/NetworkingLayerSwift6App.swift @@ -0,0 +1,35 @@ +/* + MIT License + + Copyright (c) 2024 Egzon Pllana + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import SwiftUI + +@main +struct NetworkingLayerSwift6App: App { + + var body: some Scene { + WindowGroup { + HomeView() + } + } +} diff --git a/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeView.swift b/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeView.swift new file mode 100644 index 0000000..607bcbd --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeView.swift @@ -0,0 +1,60 @@ +// +// ContentView.swift +// +// Created by Egzon Pllana. +// + +import SwiftUI + +struct HomeView: View { + + // MARK: - Properties - + @StateObject private var viewModel = HomeViewModel() + + // MARK: - View - + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + VStack { + Text("Posts: \(viewModel.posts.count)") + Text("Upload progress: \(String(format: "%.0f%%", viewModel.uploadProgress * 100))") + } + .padding() + } + .padding() + .onAppear { + // Enable any for testing. + // ----------------------- + getPosts() + // createPost() + // uploadImage() + } + } + + // MARK: - Methods - + private func getPosts() { + Task { + try await viewModel.getPosts() + print("[Task] Get posts finished.") + } + } + + private func createPost() { + Task { + try await viewModel.createPost() + print("[Task] Create post finished.") + } + } + + private func uploadImage() { + Task { + try await viewModel.uploadImage() + print("[Task] Upload image finished.") + } + } +} + +#Preview { + HomeView() +} diff --git a/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModel.swift b/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModel.swift new file mode 100644 index 0000000..093b670 --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModel.swift @@ -0,0 +1,59 @@ +// +// HomeViewModel.swift +// +// Created by Egzon Pllana. +// + +import UIKit + +final class HomeViewModel: HomeViewModelProtocol { + + // MARK: - Publishers - + @Published var posts: [PostDTO] = [] + @Published var uploadProgress: Double = 0.0 + + // MARK: - Properties - + private let postsRepositoryUseCase: any PostsRepositoryUseCaseProtocol + + // MARK: - Initialization - + init( + postsRepositoryUseCase: any PostsRepositoryUseCaseProtocol = PostsRepositoryUseCase() + ) { + self.postsRepositoryUseCase = postsRepositoryUseCase + } + + // MARK: - Methods - + @discardableResult + func getPosts() async throws -> [PostDTO] { + self.posts = try await postsRepositoryUseCase.getPosts() + return posts + } + + func createPost() async throws { + try await postsRepositoryUseCase.createPost() + } + + func uploadImage() async throws { + guard let image = UIImage(named: PostConstants.smallImageName), + let data = image.jpegData(compressionQuality: 1.0) else { + return + } + + // Create the progress delegate inline. + let progressDelegate = UploadProgressDelegate { [weak self] progress in + // Update progress on the main thread. + Task { @MainActor in + self?.uploadProgress = progress + } + } + + // Pass the progress delegate to the upload method. + // Note: current free API that we use do not support uploading multi part data, it will report the 503 status code. + try await postsRepositoryUseCase.uploadImage( + data: data, + fileName: PostConstants.imageName, + mimeType: ImageMimeType.png.asString, + progressDelegate: progressDelegate + ) + } +} diff --git a/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModelProtocol.swift b/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModelProtocol.swift new file mode 100644 index 0000000..29435bf --- /dev/null +++ b/NetworkingLayerSwift6/Architecture/Presentation/Home/HomeViewModelProtocol.swift @@ -0,0 +1,50 @@ +// +// HomeViewModelProtocol.swift +// +// Created by Egzon Pllana. +// + +import Foundation + +/// A protocol defining the interface for a Home ViewModel. +/// +/// The `HomeViewModelProtocol` provides properties and methods for managing posts and uploading images. +/// +/// - Conforms to: `MainActor` +/// - Requires: `ObservableObject` to support SwiftUI's data-binding and reactivity. +@MainActor +protocol HomeViewModelProtocol: ObservableObject, Sendable { + + /// A list of posts managed by the ViewModel. + /// + /// This property provides the current collection of posts. + /// It is expected to be updated as posts are fetched or created. + var posts: [PostDTO] { get } + + /// A read-only computed property for the upload progress. + var uploadProgress: Double { get } + + /// Fetches a list of posts from the API. + /// + /// This method asynchronously retrieves posts from the API and returns them. + /// + /// - Returns: An array of `PostDTO` representing the fetched posts. + /// - Throws: An error if the request fails or data cannot be parsed. + @discardableResult + func getPosts() async throws -> [PostDTO] + + /// Creates a new post. + /// + /// This method asynchronously creates a new post by sending data to the API. + /// + /// - Throws: An error if the creation request fails. + func createPost() async throws + + /// Uploads an image to the API with progress tracking. + /// + /// This method asynchronously uploads an image to the API. It also supports progress tracking + /// through a `progressDelegate`, if provided. + /// + /// - Throws: An error if the upload fails. + func uploadImage() async throws +} diff --git a/NetworkingLayerSwift6/Preview Content/Preview Assets.xcassets/Contents.json b/NetworkingLayerSwift6/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/NetworkingLayerSwift6/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NetworkingLayerSwift6/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/NetworkingLayerSwift6/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/NetworkingLayerSwift6/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NetworkingLayerSwift6/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/NetworkingLayerSwift6/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/NetworkingLayerSwift6/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NetworkingLayerSwift6/Resources/Assets.xcassets/Contents.json b/NetworkingLayerSwift6/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/NetworkingLayerSwift6/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/NetworkingLayerSwift6/Resources/image-png.png b/NetworkingLayerSwift6/Resources/image-png.png new file mode 100644 index 0000000000000000000000000000000000000000..5003ca1f7077111b5462cc9b0792458fed71b8c5 GIT binary patch literal 60440 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

    8=pJ%a#XP7jlVw7% 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%0D4pk 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 Sds6E+fyWYOhpjR_edQl2sD+CF literal 0 HcmV?d00001 diff --git a/NetworkingLayerSwift6Tests/APIClientTests.swift b/NetworkingLayerSwift6Tests/APIClientTests.swift new file mode 100644 index 0000000..becf166 --- /dev/null +++ b/NetworkingLayerSwift6Tests/APIClientTests.swift @@ -0,0 +1,265 @@ +// +// APIClientTests.swift +// +// Created by Egzon Pllana. +// + +import XCTest +@testable import NetworkingLayerSwift6 + +// Define a mock endpoint to test +private struct MockAPIEndpoint: APIEndpointProtocol { + var method: HTTPMethod + var path: String + var baseURL: String + var headers: [String: String] + var urlParams: [String: any CustomStringConvertible] + var body: Data? + var apiVersion: APIVersion +} + +private struct MockAPIClient: APIClientProtocol { + + var mockedData: Data? + var mockedError: Error? + + var shouldSimulateRequestError: Bool = false + func request( + _ endpoint: any APIEndpointProtocol, + decoder: JSONDecoder + ) async throws -> T { + if shouldSimulateRequestError { + throw URLError(.badServerResponse) + } + + if let error = mockedError { + throw error + } + guard let data = mockedData else { + throw NSError(domain: "MockErrorDomain", code: 0, userInfo: nil) + } + return try decoder.decode(T.self, from: data) + } + + func requestVoid( + _ endpoint: any APIEndpointProtocol + ) async throws { + if shouldSimulateRequestError { + throw URLError(.badServerResponse) + } + + if let error = mockedError { + throw error + } + } + + func requestWithAlamofire( + _ endpoint: any APIEndpointProtocol, + decoder: JSONDecoder + ) async throws -> T where T : Decodable, T : Sendable { + if shouldSimulateRequestError { + throw URLError(.badServerResponse) + } + + if let error = mockedError { + throw error + } + guard let data = mockedData else { + throw NSError(domain: "MockErrorDomain", code: 0, userInfo: nil) + } + return try decoder.decode(T.self, from: data) + } + + @discardableResult + func requestWithProgress( + _ endpoint: any APIEndpointProtocol, + progressDelegate: (any UploadProgressDelegateProtocol)? + ) async throws -> Data? { + if shouldSimulateRequestError { + throw URLError(.badServerResponse) + } + + if let error = mockedError { + throw error + } + return mockedData + } +} + +// Given +private struct User: Codable, Sendable { + let id: Int + let name: String +} + +final class APIClientTests: XCTestCase { + + private var apiClient: MockAPIClient! + + override func setUp() { + super.setUp() + apiClient = MockAPIClient() + } + + func testRequest_success() async throws { + + let jsonData = "{\"id\":1,\"name\":\"John\"}".data(using: .utf8) + apiClient.mockedData = jsonData + + // When + let user: User = try await apiClient.request(MockAPIEndpoint( + method: .get, + path: "/users/1", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + ), decoder: JSONDecoder()) + + // Then + XCTAssertEqual(user.id, 1) + XCTAssertEqual(user.name, "John") + } + + func testRequest_failure() async { + // Given + let expectedError = NSError(domain: "MockErrorDomain", code: 1, userInfo: nil) + apiClient.mockedError = expectedError + + // When + do { + let _: User = try await apiClient.request(MockAPIEndpoint( + method: .get, + path: "/users/1", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + ), decoder: JSONDecoder()) + XCTFail("Expected error to be thrown") + } catch let error as NSError { + // Then + XCTAssertEqual(error.domain, expectedError.domain) + XCTAssertEqual(error.code, expectedError.code) + } + } + + func testRequestData_success() async throws { + // Given + let responseData = "some data".data(using: .utf8) + apiClient.mockedData = responseData + + // When + let data = try await apiClient.requestData(MockAPIEndpoint( + method: .get, + path: "/files", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + )) + + // Then + XCTAssertEqual(data, responseData) + } + + func testRequestData_failure() async { + // Given + let expectedError = NSError(domain: "MockErrorDomain", code: 1, userInfo: nil) + apiClient.mockedError = expectedError + + // When + do { + _ = try await apiClient.requestData(MockAPIEndpoint( + method: .get, + path: "/files", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + )) + XCTFail("Expected error to be thrown") + } catch let error as NSError { + // Then + XCTAssertEqual(error.domain, expectedError.domain) + XCTAssertEqual(error.code, expectedError.code) + } + } + + func testRequest_defaultDecoder() async throws { + // Given + struct User: Codable, Sendable { + let id: Int + let name: String + } + + let jsonData = "{\"id\":1,\"name\":\"John\"}".data(using: .utf8) + apiClient.mockedData = jsonData + + // When + let user: User = try await apiClient.request(MockAPIEndpoint( + method: .get, + path: "/users/1", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + )) + + // Then + XCTAssertEqual(user.id, 1) + XCTAssertEqual(user.name, "John") + } + + func testRequestData_validRequest() async throws { + // Given + let expectedData = "some data".data(using: .utf8) + apiClient.mockedData = expectedData + + let endpoint = MockAPIEndpoint( + method: .get, + path: "/users", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + ) + + // When + let data = try await apiClient.requestWithProgress(endpoint, progressDelegate: nil) + + // Then + XCTAssertNotNil(data, "Expected data to be not nil") + XCTAssertEqual(data, expectedData, "Expected data to match the mocked data") + } + + func testRequestData_performRequestError() async { + // Given + let endpoint = MockAPIEndpoint( + method: .get, + path: "/users", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + ) + + // Set up MockAPIClient to simulate an error + apiClient = MockAPIClient(shouldSimulateRequestError: true) + + // When/Then + do { + _ = try await apiClient.requestWithProgress(endpoint, progressDelegate: nil) + XCTFail("Expected error to be thrown") + } catch { + XCTAssertEqual((error as? URLError)?.code, .badServerResponse) + } + } +} diff --git a/NetworkingLayerSwift6Tests/APIEndpointTests.swift b/NetworkingLayerSwift6Tests/APIEndpointTests.swift new file mode 100644 index 0000000..9bc0f4d --- /dev/null +++ b/NetworkingLayerSwift6Tests/APIEndpointTests.swift @@ -0,0 +1,200 @@ +// +// APIEndpointTests.swift +// +// Created by Egzon Pllana. +// + +import XCTest +@testable import NetworkingLayerSwift6 + +// Define a mock endpoint to test +private struct MockAPIEndpoint: APIEndpointProtocol { + var method: HTTPMethod + var path: String + var baseURL: String + var headers: [String: String] + var urlParams: [String: any CustomStringConvertible] + var body: Data? + var apiVersion: APIVersion +} + +// Mock MultipartFormData for testing +struct MockMultipartFormData { + let boundary: String + let fileData: Data + let fileName: String + let mimeType: String + let parameters: [String: String] + + var asHttpBodyData: Data? { + // Mock implementation for testing purposes + return fileData // Simplified for example + } +} + + +private func isValidURL(_ url: URL) -> Bool { + // Perform basic validation or use URL validator + // For example, check if URL is reachable + return UIApplication.shared.canOpenURL(url) +} + +final class APIEndpointTests: XCTestCase { + + func testURLRequestConstruction_success() { + // Given + let endpoint = MockAPIEndpoint( + method: .get, + path: "users", + baseURL: "https://api.example.com", + headers: ["Authorization": "Bearer token"], + urlParams: ["include": "details"], + body: nil, + apiVersion: .v1 + ) + + // When + let request = endpoint.urlRequest + + // Then + XCTAssertNotNil(request) + XCTAssertEqual(request?.httpMethod, "GET") + XCTAssertEqual(request?.url?.absoluteString, "https://api.example.com/api/v1/users?include=details") + XCTAssertEqual(request?.allHTTPHeaderFields?["Authorization"], "Bearer token") + XCTAssertNil(request?.httpBody) + } + + func testURLRequestConstruction_withBody() { + // Given + let requestBody = "{\"name\":\"John\"}".data(using: .utf8) + let endpoint = MockAPIEndpoint( + method: .post, + path: "users", + baseURL: "https://api.example.com", + headers: ["Authorization": "Bearer token", "Content-Type": "application/json"], + urlParams: [:], + body: requestBody, + apiVersion: .v1 + ) + + // When + let request = endpoint.urlRequest + + // Then + XCTAssertNotNil(request) + XCTAssertEqual(request?.httpMethod, "POST") + XCTAssertEqual(request!.url!.absoluteString, "https://api.example.com/api/v1/users?") + XCTAssertEqual(request?.allHTTPHeaderFields?["Authorization"], "Bearer token") + XCTAssertEqual(request?.allHTTPHeaderFields?["Content-Type"], "application/json") + XCTAssertEqual(request?.httpBody, requestBody) + } + + func testURLRequestConstruction_noURLParams() { + // Given + let endpoint = MockAPIEndpoint( + method: .delete, + path: "users/1", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + ) + + // When + let request = endpoint.urlRequest + + // Then + XCTAssertNotNil(request) + XCTAssertEqual(request?.httpMethod, "DELETE") + XCTAssertEqual(request?.url?.absoluteString, "https://api.example.com/api/v1/users/1?") + XCTAssertNil(request?.httpBody) + } + + func testURLRequestConstruction_invalidURL() { + // Given + let endpoint = MockAPIEndpoint( + method: .get, + path: "/users", + baseURL: "invalid-url", // Invalid URL + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + ) + + // When + let request = endpoint.urlRequest + + // Then + XCTAssertNotNil(request, "Expected URLRequest to be non-nil") + + // Additional validation + // Ensure that the URL is invalid + if let url = request?.url { + XCTAssertFalse(isValidURL(url), "Expected URL to be invalid but got a valid URL") + } else { + XCTFail("URLRequest URL should not be nil") + } + } + + func testURLRequestConstruction_withEmptyPath() { + // Given + let endpoint = MockAPIEndpoint( + method: .get, + path: "", + baseURL: "https://api.example.com", + headers: [:], + urlParams: [:], + body: nil, + apiVersion: .v1 + ) + + // When + let request = endpoint.urlRequest + + // Then + XCTAssertNotNil(request) + XCTAssertEqual(request?.url?.absoluteString, "https://api.example.com/api/v1/?") + } + + func testBody_createPost() { + // Given + let postDTO: PostDTO = .init(userId: 1, title: "Title", body: "Body") + let endpoint = APIEndpoint.createPost(postDTO) + + // When + let bodyData = endpoint.body + + // Then + let expectedData = postDTO.toJSONData() + XCTAssertEqual(bodyData, expectedData) + } + + func testBody_uploadImage() { + // Given + let imageData = Data(repeating: 0, count: 10) // Mock image data + let fileName = "test.jpg" + let mimeType = "image/jpeg" + let endpoint = APIEndpoint.uploadImage(data: imageData, fileName: fileName, mimeType: ImageMimeType(rawValue: mimeType) ?? .jpeg) + + // When + let bodyData = endpoint.body + + // Then + // Here, you would typically verify the boundary and the multipart form data structure. + // For simplicity, checking if `bodyData` is not nil is a basic test. + XCTAssertNotNil(bodyData) + } + + func testBody_getPosts() { + // Given + let endpoint = APIEndpoint.getPosts + + // When + let bodyData = endpoint.body + + // Then + XCTAssertNil(bodyData) + } +} diff --git a/README.md b/README.md index 042872c..89bd4b0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# NetworkLayerSwift6 -Building a generic, thread-safe Networking Layer in Swift 6, using the latest Swift concurrency APIs and thread-safe methods to avoid multi-threading issues or crashes. +# NetworkingLayerSwift6 +Building a thread-safe Networking layer with Swift 6, empowering the use of Sendable protocol and async await.