diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 4dfa38b0..54e93b41 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -3,61 +3,101 @@ archiveVersion = 1; classes = { }; - objectVersion = 48; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ - 1BFDF59DD46728BDB2EC91BC /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3E91FBEBEFBA80535BCF2 /* Pods_Example.framework */; }; - 4F17500F20776B0F0063C67D /* ListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17500E20776B0F0063C67D /* ListService.swift */; }; - 4F17501120776C500063C67D /* AnimalEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17501020776C500063C67D /* AnimalEntity.swift */; }; - 4F17501320776C9E0063C67D /* GetAnimalPagiongRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17501220776C9E0063C67D /* GetAnimalPagiongRequest.swift */; }; - 4F175017207B25A00063C67D /* AnimalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F175016207B25A00063C67D /* AnimalViewController.swift */; }; - 4F175019207B26B20063C67D /* AnimalCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4F175018207B26B20063C67D /* AnimalCell.xib */; }; - 4F17501B207B276E0063C67D /* AnimalCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17501A207B276E0063C67D /* AnimalCell.swift */; }; - 4F17501D207B27B90063C67D /* AnimalCellGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17501C207B27B90063C67D /* AnimalCellGenerator.swift */; }; - 4F1DDBD3207B2D2500E0CEB4 /* AnimalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F1DDBD2207B2D2500E0CEB4 /* AnimalPresenter.swift */; }; + 4F17501B207B276E0063C67D /* PaginationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17501A207B276E0063C67D /* PaginationCell.swift */; }; + 4F17501D207B27B90063C67D /* PaginationCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F17501C207B27B90063C67D /* PaginationCellViewModel.swift */; }; + 4F1DDBD3207B2D2500E0CEB4 /* PaginationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F1DDBD2207B2D2500E0CEB4 /* PaginationPresenter.swift */; }; 4FA424222075D346004AEDF6 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA424202075D346004AEDF6 /* Message.swift */; }; 4FA424232075D346004AEDF6 /* Message.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4FA424212075D346004AEDF6 /* Message.xib */; }; - 4FA424262075D369004AEDF6 /* AuthRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA424242075D369004AEDF6 /* AuthRequest.swift */; }; - 4FA424272075D369004AEDF6 /* Urls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA424252075D369004AEDF6 /* Urls.swift */; }; - 4FA424302075DBC4004AEDF6 /* AuthTokenEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA4242F2075DBC4004AEDF6 /* AuthTokenEntity.swift */; }; + 504E74BC2BE37EA400CFEB2E /* MockServer in Frameworks */ = {isa = PBXBuildFile; productRef = 504E74BB2BE37EA400CFEB2E /* MockServer */; }; + 504E74BE2BE37EA800CFEB2E /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 504E74BD2BE37EA800CFEB2E /* Models */; }; + 504E74C02BE37EAB00CFEB2E /* Services in Frameworks */ = {isa = PBXBuildFile; productRef = 504E74BF2BE37EAB00CFEB2E /* Services */; }; + 50816A1D2BC6E6DE00A43F3D /* NodeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 50816A1C2BC6E6DE00A43F3D /* NodeKit */; }; + 5097DF442BCD624700D422EE /* NodeKitMock in Frameworks */ = {isa = PBXBuildFile; productRef = 5097DF432BCD624700D422EE /* NodeKitMock */; }; + 5097DF502BCD641D00D422EE /* LoginRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF4F2BCD641D00D422EE /* LoginRouter.swift */; }; + 5097DF522BCD65E600D422EE /* FeatureListConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF512BCD65E600D422EE /* FeatureListConfigurator.swift */; }; + 5097DF562BCD666000D422EE /* FeatureListViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5097DF552BCD666000D422EE /* FeatureListViewController.storyboard */; }; + 5097DF592BCD66C400D422EE /* FeatureListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF582BCD66C400D422EE /* FeatureListViewController.swift */; }; + 5097DF5C2BCD67AF00D422EE /* UIStoryboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF5B2BCD67AF00D422EE /* UIStoryboard+Extension.swift */; }; + 5097DF5E2BCD692800D422EE /* ErrorRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF5D2BCD692800D422EE /* ErrorRepresentable.swift */; }; + 5097DF602BCD6A3600D422EE /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF5F2BCD6A3600D422EE /* Credentials.swift */; }; + 5097DF622BCD6BB000D422EE /* LoginConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF612BCD6BB000D422EE /* LoginConfigurator.swift */; }; + 5097DF642BCD6E3300D422EE /* LoginViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5097DF632BCD6E3300D422EE /* LoginViewController.storyboard */; }; + 5097DF662BCD73D700D422EE /* FeatureCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF652BCD73D700D422EE /* FeatureCell.swift */; }; + 5097DF682BCD73E100D422EE /* FeatureCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5097DF672BCD73E100D422EE /* FeatureCell.xib */; }; + 5097DF6E2BCD79C300D422EE /* FeatureCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF6D2BCD79C300D422EE /* FeatureCellViewModel.swift */; }; + 5097DF702BCD7B0500D422EE /* FeatureListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF6F2BCD7B0500D422EE /* FeatureListPresenter.swift */; }; + 5097DF722BCD7B7400D422EE /* FeatureListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF712BCD7B7300D422EE /* FeatureListRouter.swift */; }; + 5097DF762BCD8AE400D422EE /* PaginationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF752BCD8AE400D422EE /* PaginationViewController.swift */; }; + 5097DF7A2BCD8B1300D422EE /* PaginationViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5097DF792BCD8B1300D422EE /* PaginationViewController.storyboard */; }; + 5097DF7E2BCD8C9100D422EE /* PaginationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF7D2BCD8C9100D422EE /* PaginationRouter.swift */; }; + 5097DF802BCD8FD400D422EE /* PaginationConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF7F2BCD8FD400D422EE /* PaginationConfigurator.swift */; }; + 5097DF832BCDA57700D422EE /* GroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF822BCDA57700D422EE /* GroupViewController.swift */; }; + 5097DF852BCDA58C00D422EE /* GroupViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5097DF842BCDA58C00D422EE /* GroupViewController.storyboard */; }; + 5097DF872BCDA5A300D422EE /* GroupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF862BCDA5A300D422EE /* GroupPresenter.swift */; }; + 5097DF892BCDA5B900D422EE /* GroupConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF882BCDA5B900D422EE /* GroupConfigurator.swift */; }; + 5097DFB22BCE00AB00D422EE /* GroupViewModelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DFB12BCE00AB00D422EE /* GroupViewModelProvider.swift */; }; + 50B569802BD183F00054DC09 /* Utils in Frameworks */ = {isa = PBXBuildFile; productRef = 50B5697F2BD183F00054DC09 /* Utils */; }; + 50B569832BD184150054DC09 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = 50B569822BD184150054DC09 /* Nuke */; }; + 50B569852BD184AD0054DC09 /* NukeExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 50B569842BD184AD0054DC09 /* NukeExtensions */; }; + 50B569872BD186BB0054DC09 /* PaginationCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50B569862BD186BB0054DC09 /* PaginationCell.xib */; }; + 50B569892BD188D80054DC09 /* BaseLoadingSubview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B569882BD188D80054DC09 /* BaseLoadingSubview.swift */; }; + 50B5698C2BD18F040054DC09 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 50B5698B2BD18F040054DC09 /* SnapKit */; }; + 50C445A22BC81FB300C515E6 /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 50C445A12BC81FB300C515E6 /* SwiftMessages */; }; + 50C445A82BC8202700C515E6 /* ReactiveDataDisplayManager in Frameworks */ = {isa = PBXBuildFile; productRef = 50C445A72BC8202700C515E6 /* ReactiveDataDisplayManager */; }; + 50C445CE2BC8736B00C515E6 /* PaginationLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C445CD2BC8736B00C515E6 /* PaginationLoadingView.swift */; }; B96CD44D1FDC5B1600F5A107 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96CD44C1FDC5B1600F5A107 /* AppDelegate.swift */; }; B96CD44F1FDC5B1600F5A107 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96CD44E1FDC5B1600F5A107 /* LoginViewController.swift */; }; - B96CD4521FDC5B1600F5A107 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B96CD4501FDC5B1600F5A107 /* Main.storyboard */; }; B96CD4541FDC5B1600F5A107 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B96CD4531FDC5B1600F5A107 /* Assets.xcassets */; }; B96CD4571FDC5B1600F5A107 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B96CD4551FDC5B1600F5A107 /* LaunchScreen.storyboard */; }; B97D3E5C1FDC83950039DB92 /* LoginPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97D3E5B1FDC83950039DB92 /* LoginPresenter.swift */; }; - B97D3E5F1FDC85440039DB92 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97D3E5E1FDC85440039DB92 /* AuthService.swift */; }; - B99A70491FDD264200AB47DD /* UserEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99A70481FDD264200AB47DD /* UserEntity.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 3FCB3014DE0C159D06E83F1A /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; - 4F17500E20776B0F0063C67D /* ListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListService.swift; sourceTree = ""; }; - 4F17501020776C500063C67D /* AnimalEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimalEntity.swift; sourceTree = ""; }; - 4F17501220776C9E0063C67D /* GetAnimalPagiongRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAnimalPagiongRequest.swift; sourceTree = ""; }; - 4F175016207B25A00063C67D /* AnimalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimalViewController.swift; sourceTree = ""; }; - 4F175018207B26B20063C67D /* AnimalCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AnimalCell.xib; sourceTree = ""; }; - 4F17501A207B276E0063C67D /* AnimalCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimalCell.swift; sourceTree = ""; }; - 4F17501C207B27B90063C67D /* AnimalCellGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimalCellGenerator.swift; sourceTree = ""; }; - 4F1DDBD2207B2D2500E0CEB4 /* AnimalPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimalPresenter.swift; sourceTree = ""; }; + 4F17501A207B276E0063C67D /* PaginationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationCell.swift; sourceTree = ""; }; + 4F17501C207B27B90063C67D /* PaginationCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationCellViewModel.swift; sourceTree = ""; }; + 4F1DDBD2207B2D2500E0CEB4 /* PaginationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationPresenter.swift; sourceTree = ""; }; 4FA424202075D346004AEDF6 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; 4FA424212075D346004AEDF6 /* Message.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Message.xib; sourceTree = ""; }; - 4FA424242075D369004AEDF6 /* AuthRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthRequest.swift; sourceTree = ""; }; - 4FA424252075D369004AEDF6 /* Urls.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Urls.swift; sourceTree = ""; }; - 4FA4242F2075DBC4004AEDF6 /* AuthTokenEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTokenEntity.swift; sourceTree = ""; }; - 94F1A2877B4E5A8ABA83AED8 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; - B5A3E91FBEBEFBA80535BCF2 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 509656A72BE37DD700808890 /* Services */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Services; path = Modules/Services; sourceTree = ""; }; + 509656A82BE37DD700808890 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Modules/Models; sourceTree = ""; }; + 509656A92BE37DD700808890 /* MockServer */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MockServer; path = Modules/MockServer; sourceTree = ""; }; + 5097DF4F2BCD641D00D422EE /* LoginRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouter.swift; sourceTree = ""; }; + 5097DF512BCD65E600D422EE /* FeatureListConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureListConfigurator.swift; sourceTree = ""; }; + 5097DF552BCD666000D422EE /* FeatureListViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = FeatureListViewController.storyboard; sourceTree = ""; }; + 5097DF582BCD66C400D422EE /* FeatureListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureListViewController.swift; sourceTree = ""; }; + 5097DF5B2BCD67AF00D422EE /* UIStoryboard+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+Extension.swift"; sourceTree = ""; }; + 5097DF5D2BCD692800D422EE /* ErrorRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorRepresentable.swift; sourceTree = ""; }; + 5097DF5F2BCD6A3600D422EE /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; + 5097DF612BCD6BB000D422EE /* LoginConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginConfigurator.swift; sourceTree = ""; }; + 5097DF632BCD6E3300D422EE /* LoginViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LoginViewController.storyboard; sourceTree = ""; }; + 5097DF652BCD73D700D422EE /* FeatureCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureCell.swift; sourceTree = ""; }; + 5097DF672BCD73E100D422EE /* FeatureCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FeatureCell.xib; sourceTree = ""; }; + 5097DF6D2BCD79C300D422EE /* FeatureCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureCellViewModel.swift; sourceTree = ""; }; + 5097DF6F2BCD7B0500D422EE /* FeatureListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureListPresenter.swift; sourceTree = ""; }; + 5097DF712BCD7B7300D422EE /* FeatureListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureListRouter.swift; sourceTree = ""; }; + 5097DF752BCD8AE400D422EE /* PaginationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationViewController.swift; sourceTree = ""; }; + 5097DF792BCD8B1300D422EE /* PaginationViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PaginationViewController.storyboard; sourceTree = ""; }; + 5097DF7D2BCD8C9100D422EE /* PaginationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationRouter.swift; sourceTree = ""; }; + 5097DF7F2BCD8FD400D422EE /* PaginationConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationConfigurator.swift; sourceTree = ""; }; + 5097DF822BCDA57700D422EE /* GroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupViewController.swift; sourceTree = ""; }; + 5097DF842BCDA58C00D422EE /* GroupViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = GroupViewController.storyboard; sourceTree = ""; }; + 5097DF862BCDA5A300D422EE /* GroupPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupPresenter.swift; sourceTree = ""; }; + 5097DF882BCDA5B900D422EE /* GroupConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupConfigurator.swift; sourceTree = ""; }; + 5097DFB12BCE00AB00D422EE /* GroupViewModelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupViewModelProvider.swift; sourceTree = ""; }; + 50B569862BD186BB0054DC09 /* PaginationCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PaginationCell.xib; sourceTree = ""; }; + 50B569882BD188D80054DC09 /* BaseLoadingSubview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseLoadingSubview.swift; sourceTree = ""; }; + 50C445CD2BC8736B00C515E6 /* PaginationLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationLoadingView.swift; sourceTree = ""; }; B96CD4491FDC5B1600F5A107 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; B96CD44C1FDC5B1600F5A107 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B96CD44E1FDC5B1600F5A107 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; - B96CD4511FDC5B1600F5A107 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B96CD4531FDC5B1600F5A107 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B96CD4561FDC5B1600F5A107 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B96CD4581FDC5B1600F5A107 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B97D3E5B1FDC83950039DB92 /* LoginPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPresenter.swift; sourceTree = ""; }; - B97D3E5E1FDC85440039DB92 /* AuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; - B99A70481FDD264200AB47DD /* UserEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntity.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,67 +105,154 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1BFDF59DD46728BDB2EC91BC /* Pods_Example.framework in Frameworks */, + 504E74BE2BE37EA800CFEB2E /* Models in Frameworks */, + 504E74BC2BE37EA400CFEB2E /* MockServer in Frameworks */, + 50B569852BD184AD0054DC09 /* NukeExtensions in Frameworks */, + 50C445A82BC8202700C515E6 /* ReactiveDataDisplayManager in Frameworks */, + 5097DF442BCD624700D422EE /* NodeKitMock in Frameworks */, + 50C445A22BC81FB300C515E6 /* SwiftMessages in Frameworks */, + 50B569832BD184150054DC09 /* Nuke in Frameworks */, + 50B5698C2BD18F040054DC09 /* SnapKit in Frameworks */, + 50816A1D2BC6E6DE00A43F3D /* NodeKit in Frameworks */, + 504E74C02BE37EAB00CFEB2E /* Services in Frameworks */, + 50B569802BD183F00054DC09 /* Utils in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 31D9204D1E74E18ACDA9BD08 /* Frameworks */ = { + 509656A62BE37DCC00808890 /* Modules */ = { isa = PBXGroup; children = ( - B5A3E91FBEBEFBA80535BCF2 /* Pods_Example.framework */, + 509656A92BE37DD700808890 /* MockServer */, + 509656A82BE37DD700808890 /* Models */, + 509656A72BE37DD700808890 /* Services */, ); - name = Frameworks; + name = Modules; + sourceTree = ""; + }; + 5097DF4C2BCD63AA00D422EE /* Flows */ = { + isa = PBXGroup; + children = ( + 5097DF4E2BCD63DE00D422EE /* FeatureListFlow */, + 5097DF4D2BCD63C500D422EE /* AuthFlow */, + ); + path = Flows; + sourceTree = ""; + }; + 5097DF4D2BCD63C500D422EE /* AuthFlow */ = { + isa = PBXGroup; + children = ( + B96CD44E1FDC5B1600F5A107 /* LoginViewController.swift */, + B97D3E5B1FDC83950039DB92 /* LoginPresenter.swift */, + 5097DF4F2BCD641D00D422EE /* LoginRouter.swift */, + 5097DF5F2BCD6A3600D422EE /* Credentials.swift */, + 5097DF612BCD6BB000D422EE /* LoginConfigurator.swift */, + 5097DF632BCD6E3300D422EE /* LoginViewController.storyboard */, + ); + path = AuthFlow; + sourceTree = ""; + }; + 5097DF4E2BCD63DE00D422EE /* FeatureListFlow */ = { + isa = PBXGroup; + children = ( + 5097DF572BCD669C00D422EE /* Cells */, + 5097DF512BCD65E600D422EE /* FeatureListConfigurator.swift */, + 5097DF552BCD666000D422EE /* FeatureListViewController.storyboard */, + 5097DF582BCD66C400D422EE /* FeatureListViewController.swift */, + 5097DF6F2BCD7B0500D422EE /* FeatureListPresenter.swift */, + 5097DF712BCD7B7300D422EE /* FeatureListRouter.swift */, + ); + path = FeatureListFlow; + sourceTree = ""; + }; + 5097DF572BCD669C00D422EE /* Cells */ = { + isa = PBXGroup; + children = ( + 5097DF652BCD73D700D422EE /* FeatureCell.swift */, + 5097DF672BCD73E100D422EE /* FeatureCell.xib */, + 5097DF6D2BCD79C300D422EE /* FeatureCellViewModel.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 5097DF5A2BCD67A400D422EE /* Extensions */ = { + isa = PBXGroup; + children = ( + 5097DF5B2BCD67AF00D422EE /* UIStoryboard+Extension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 5097DF732BCD8AB500D422EE /* PaginationFeature */ = { + isa = PBXGroup; + children = ( + 5097DF7C2BCD8BAA00D422EE /* Subviews */, + 5097DF7B2BCD8B3700D422EE /* Cells */, + 5097DF752BCD8AE400D422EE /* PaginationViewController.swift */, + 5097DF792BCD8B1300D422EE /* PaginationViewController.storyboard */, + 4F1DDBD2207B2D2500E0CEB4 /* PaginationPresenter.swift */, + 5097DF7D2BCD8C9100D422EE /* PaginationRouter.swift */, + 5097DF7F2BCD8FD400D422EE /* PaginationConfigurator.swift */, + ); + path = PaginationFeature; + sourceTree = ""; + }; + 5097DF742BCD8AD000D422EE /* Features */ = { + isa = PBXGroup; + children = ( + 5097DF812BCDA56400D422EE /* GroupFeature */, + 5097DF732BCD8AB500D422EE /* PaginationFeature */, + ); + path = Features; sourceTree = ""; }; - 4F17500C20776AE20063C67D /* UserService */ = { + 5097DF7B2BCD8B3700D422EE /* Cells */ = { isa = PBXGroup; children = ( - 4FA424242075D369004AEDF6 /* AuthRequest.swift */, - B97D3E5E1FDC85440039DB92 /* AuthService.swift */, + 50B569862BD186BB0054DC09 /* PaginationCell.xib */, + 4F17501C207B27B90063C67D /* PaginationCellViewModel.swift */, + 4F17501A207B276E0063C67D /* PaginationCell.swift */, + 50B569882BD188D80054DC09 /* BaseLoadingSubview.swift */, ); - path = UserService; + path = Cells; sourceTree = ""; }; - 4F17500D20776AF80063C67D /* ListService */ = { + 5097DF7C2BCD8BAA00D422EE /* Subviews */ = { isa = PBXGroup; children = ( - 4F17500E20776B0F0063C67D /* ListService.swift */, - 4F17501220776C9E0063C67D /* GetAnimalPagiongRequest.swift */, + 50C445CD2BC8736B00C515E6 /* PaginationLoadingView.swift */, ); - path = ListService; + path = Subviews; sourceTree = ""; }; - 4F175015207B18310063C67D /* Animals */ = { + 5097DF812BCDA56400D422EE /* GroupFeature */ = { isa = PBXGroup; children = ( - 4F175016207B25A00063C67D /* AnimalViewController.swift */, - 4F175018207B26B20063C67D /* AnimalCell.xib */, - 4F17501A207B276E0063C67D /* AnimalCell.swift */, - 4F17501C207B27B90063C67D /* AnimalCellGenerator.swift */, - 4F1DDBD2207B2D2500E0CEB4 /* AnimalPresenter.swift */, + 5097DF822BCDA57700D422EE /* GroupViewController.swift */, + 5097DF842BCDA58C00D422EE /* GroupViewController.storyboard */, + 5097DF862BCDA5A300D422EE /* GroupPresenter.swift */, + 5097DF882BCDA5B900D422EE /* GroupConfigurator.swift */, + 5097DFB12BCE00AB00D422EE /* GroupViewModelProvider.swift */, ); - path = Animals; + path = GroupFeature; sourceTree = ""; }; - 8D9A3BAD7E31358D3BB50951 /* Pods */ = { + 50C445B52BC8363F00C515E6 /* Frameworks */ = { isa = PBXGroup; children = ( - 94F1A2877B4E5A8ABA83AED8 /* Pods-Example.debug.xcconfig */, - 3FCB3014DE0C159D06E83F1A /* Pods-Example.release.xcconfig */, ); - name = Pods; + name = Frameworks; sourceTree = ""; }; B96CD4401FDC5B1600F5A107 = { isa = PBXGroup; children = ( + 509656A62BE37DCC00808890 /* Modules */, B96CD44B1FDC5B1600F5A107 /* Example */, B96CD44A1FDC5B1600F5A107 /* Products */, - 8D9A3BAD7E31358D3BB50951 /* Pods */, - 31D9204D1E74E18ACDA9BD08 /* Frameworks */, + 50C445B52BC8363F00C515E6 /* Frameworks */, ); sourceTree = ""; }; @@ -140,13 +267,11 @@ B96CD44B1FDC5B1600F5A107 /* Example */ = { isa = PBXGroup; children = ( - B99A70471FDD263700AB47DD /* Models */, + 5097DF742BCD8AD000D422EE /* Features */, + 5097DF5A2BCD67A400D422EE /* Extensions */, + 5097DF4C2BCD63AA00D422EE /* Flows */, B99A70401FDD18C900AB47DD /* Utils */, - B97D3E5D1FDC85360039DB92 /* Services */, - B97D3E5A1FDC837F0039DB92 /* Login */, - 4F175015207B18310063C67D /* Animals */, B96CD44C1FDC5B1600F5A107 /* AppDelegate.swift */, - B96CD4501FDC5B1600F5A107 /* Main.storyboard */, B96CD4531FDC5B1600F5A107 /* Assets.xcassets */, B96CD4551FDC5B1600F5A107 /* LaunchScreen.storyboard */, B96CD4581FDC5B1600F5A107 /* Info.plist */, @@ -154,44 +279,16 @@ path = Example; sourceTree = ""; }; - B97D3E5A1FDC837F0039DB92 /* Login */ = { - isa = PBXGroup; - children = ( - B96CD44E1FDC5B1600F5A107 /* LoginViewController.swift */, - B97D3E5B1FDC83950039DB92 /* LoginPresenter.swift */, - ); - path = Login; - sourceTree = ""; - }; - B97D3E5D1FDC85360039DB92 /* Services */ = { - isa = PBXGroup; - children = ( - 4F17500D20776AF80063C67D /* ListService */, - 4F17500C20776AE20063C67D /* UserService */, - 4FA424252075D369004AEDF6 /* Urls.swift */, - ); - path = Services; - sourceTree = ""; - }; B99A70401FDD18C900AB47DD /* Utils */ = { isa = PBXGroup; children = ( 4FA424202075D346004AEDF6 /* Message.swift */, 4FA424212075D346004AEDF6 /* Message.xib */, + 5097DF5D2BCD692800D422EE /* ErrorRepresentable.swift */, ); path = Utils; sourceTree = ""; }; - B99A70471FDD263700AB47DD /* Models */ = { - isa = PBXGroup; - children = ( - B99A70481FDD264200AB47DD /* UserEntity.swift */, - 4FA4242F2075DBC4004AEDF6 /* AuthTokenEntity.swift */, - 4F17501020776C500063C67D /* AnimalEntity.swift */, - ); - path = Models; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -199,18 +296,28 @@ isa = PBXNativeTarget; buildConfigurationList = B96CD45B1FDC5B1600F5A107 /* Build configuration list for PBXNativeTarget "Example" */; buildPhases = ( - 9A4CF8D7D94F5483F6DE6360 /* [CP] Check Pods Manifest.lock */, B96CD4451FDC5B1600F5A107 /* Sources */, B96CD4461FDC5B1600F5A107 /* Frameworks */, B96CD4471FDC5B1600F5A107 /* Resources */, - D0AD27CF17E2E8B5F0A139FF /* [CP] Embed Pods Frameworks */, - D84F971166AC8D6ADF93A56D /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Example; + packageProductDependencies = ( + 50816A1C2BC6E6DE00A43F3D /* NodeKit */, + 50C445A12BC81FB300C515E6 /* SwiftMessages */, + 50C445A72BC8202700C515E6 /* ReactiveDataDisplayManager */, + 5097DF432BCD624700D422EE /* NodeKitMock */, + 50B5697F2BD183F00054DC09 /* Utils */, + 50B569822BD184150054DC09 /* Nuke */, + 50B569842BD184AD0054DC09 /* NukeExtensions */, + 50B5698B2BD18F040054DC09 /* SnapKit */, + 504E74BB2BE37EA400CFEB2E /* MockServer */, + 504E74BD2BE37EA800CFEB2E /* Models */, + 504E74BF2BE37EAB00CFEB2E /* Services */, + ); productName = Example; productReference = B96CD4491FDC5B1600F5A107 /* Example.app */; productType = "com.apple.product-type.application"; @@ -240,6 +347,14 @@ Base, ); mainGroup = B96CD4401FDC5B1600F5A107; + packageReferences = ( + 50C445A02BC81FB300C515E6 /* XCRemoteSwiftPackageReference "SwiftMessages" */, + 50C445A62BC8202600C515E6 /* XCRemoteSwiftPackageReference "ReactiveDataDisplayManager" */, + 5097DF472BCD62AA00D422EE /* XCLocalSwiftPackageReference "../NodeKit" */, + 50B5697E2BD183F00054DC09 /* XCRemoteSwiftPackageReference "iOS-Utils" */, + 50B569812BD184150054DC09 /* XCRemoteSwiftPackageReference "Nuke" */, + 50B5698A2BD18F040054DC09 /* XCRemoteSwiftPackageReference "SnapKit" */, + ); productRefGroup = B96CD44A1FDC5B1600F5A107 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -256,111 +371,56 @@ files = ( B96CD4571FDC5B1600F5A107 /* LaunchScreen.storyboard in Resources */, 4FA424232075D346004AEDF6 /* Message.xib in Resources */, + 50B569872BD186BB0054DC09 /* PaginationCell.xib in Resources */, + 5097DF562BCD666000D422EE /* FeatureListViewController.storyboard in Resources */, + 5097DF7A2BCD8B1300D422EE /* PaginationViewController.storyboard in Resources */, + 5097DF852BCDA58C00D422EE /* GroupViewController.storyboard in Resources */, B96CD4541FDC5B1600F5A107 /* Assets.xcassets in Resources */, - B96CD4521FDC5B1600F5A107 /* Main.storyboard in Resources */, - 4F175019207B26B20063C67D /* AnimalCell.xib in Resources */, + 5097DF682BCD73E100D422EE /* FeatureCell.xib in Resources */, + 5097DF642BCD6E3300D422EE /* LoginViewController.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 9A4CF8D7D94F5483F6DE6360 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - D0AD27CF17E2E8B5F0A139FF /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", - "${BUILT_PRODUCTS_DIR}/AlamofireImage/AlamofireImage.framework", - "${BUILT_PRODUCTS_DIR}/CoreNetKit/CoreNetKit.framework", - "${BUILT_PRODUCTS_DIR}/ReactiveDataDisplayManager/ReactiveDataDisplayManager.framework", - "${BUILT_PRODUCTS_DIR}/SwiftMessages/SwiftMessages.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CoreNetKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactiveDataDisplayManager.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftMessages.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - D84F971166AC8D6ADF93A56D /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ B96CD4451FDC5B1600F5A107 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4F17501320776C9E0063C67D /* GetAnimalPagiongRequest.swift in Sources */, + 50B569892BD188D80054DC09 /* BaseLoadingSubview.swift in Sources */, B97D3E5C1FDC83950039DB92 /* LoginPresenter.swift in Sources */, - 4F175017207B25A00063C67D /* AnimalViewController.swift in Sources */, + 5097DF6E2BCD79C300D422EE /* FeatureCellViewModel.swift in Sources */, B96CD44F1FDC5B1600F5A107 /* LoginViewController.swift in Sources */, - B99A70491FDD264200AB47DD /* UserEntity.swift in Sources */, - 4FA424272075D369004AEDF6 /* Urls.swift in Sources */, + 50C445CE2BC8736B00C515E6 /* PaginationLoadingView.swift in Sources */, 4FA424222075D346004AEDF6 /* Message.swift in Sources */, - 4F17501120776C500063C67D /* AnimalEntity.swift in Sources */, - 4FA424302075DBC4004AEDF6 /* AuthTokenEntity.swift in Sources */, + 5097DF722BCD7B7400D422EE /* FeatureListRouter.swift in Sources */, + 5097DF502BCD641D00D422EE /* LoginRouter.swift in Sources */, + 5097DF622BCD6BB000D422EE /* LoginConfigurator.swift in Sources */, B96CD44D1FDC5B1600F5A107 /* AppDelegate.swift in Sources */, - 4F1DDBD3207B2D2500E0CEB4 /* AnimalPresenter.swift in Sources */, - 4F17500F20776B0F0063C67D /* ListService.swift in Sources */, - 4FA424262075D369004AEDF6 /* AuthRequest.swift in Sources */, - B97D3E5F1FDC85440039DB92 /* AuthService.swift in Sources */, - 4F17501B207B276E0063C67D /* AnimalCell.swift in Sources */, - 4F17501D207B27B90063C67D /* AnimalCellGenerator.swift in Sources */, + 5097DF5E2BCD692800D422EE /* ErrorRepresentable.swift in Sources */, + 4F1DDBD3207B2D2500E0CEB4 /* PaginationPresenter.swift in Sources */, + 5097DF762BCD8AE400D422EE /* PaginationViewController.swift in Sources */, + 5097DF702BCD7B0500D422EE /* FeatureListPresenter.swift in Sources */, + 5097DF802BCD8FD400D422EE /* PaginationConfigurator.swift in Sources */, + 5097DF662BCD73D700D422EE /* FeatureCell.swift in Sources */, + 5097DF592BCD66C400D422EE /* FeatureListViewController.swift in Sources */, + 5097DF832BCDA57700D422EE /* GroupViewController.swift in Sources */, + 5097DF5C2BCD67AF00D422EE /* UIStoryboard+Extension.swift in Sources */, + 5097DF7E2BCD8C9100D422EE /* PaginationRouter.swift in Sources */, + 5097DF602BCD6A3600D422EE /* Credentials.swift in Sources */, + 5097DF892BCDA5B900D422EE /* GroupConfigurator.swift in Sources */, + 5097DF872BCDA5A300D422EE /* GroupPresenter.swift in Sources */, + 4F17501B207B276E0063C67D /* PaginationCell.swift in Sources */, + 5097DFB22BCE00AB00D422EE /* GroupViewModelProvider.swift in Sources */, + 5097DF522BCD65E600D422EE /* FeatureListConfigurator.swift in Sources */, + 4F17501D207B27B90063C67D /* PaginationCellViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - B96CD4501FDC5B1600F5A107 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - B96CD4511FDC5B1600F5A107 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; B96CD4551FDC5B1600F5A107 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -422,7 +482,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -475,42 +535,47 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.1; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; B96CD45C1FDC5B1600F5A107 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 94F1A2877B4E5A8ABA83AED8 /* Pods-Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ZJ45BEVK69; INFOPLIST_FILE = Example/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = AK.ExampleCoreNetKit; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; B96CD45D1FDC5B1600F5A107 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3FCB3014DE0C159D06E83F1A /* Pods-Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ZJ45BEVK69; INFOPLIST_FILE = Example/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = AK.ExampleCoreNetKit; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -537,6 +602,109 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 5097DF472BCD62AA00D422EE /* XCLocalSwiftPackageReference "../NodeKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../NodeKit; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 50B5697E2BD183F00054DC09 /* XCRemoteSwiftPackageReference "iOS-Utils" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/surfstudio/iOS-Utils"; + requirement = { + kind = exactVersion; + version = 13.2.0; + }; + }; + 50B569812BD184150054DC09 /* XCRemoteSwiftPackageReference "Nuke" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kean/Nuke"; + requirement = { + kind = exactVersion; + version = 12.5.0; + }; + }; + 50B5698A2BD18F040054DC09 /* XCRemoteSwiftPackageReference "SnapKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SnapKit/SnapKit"; + requirement = { + kind = exactVersion; + version = 5.7.1; + }; + }; + 50C445A02BC81FB300C515E6 /* XCRemoteSwiftPackageReference "SwiftMessages" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SwiftKickMobile/SwiftMessages"; + requirement = { + kind = exactVersion; + version = 10.0.0; + }; + }; + 50C445A62BC8202600C515E6 /* XCRemoteSwiftPackageReference "ReactiveDataDisplayManager" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/surfstudio/ReactiveDataDisplayManager"; + requirement = { + kind = exactVersion; + version = 7.4.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 504E74BB2BE37EA400CFEB2E /* MockServer */ = { + isa = XCSwiftPackageProductDependency; + productName = MockServer; + }; + 504E74BD2BE37EA800CFEB2E /* Models */ = { + isa = XCSwiftPackageProductDependency; + productName = Models; + }; + 504E74BF2BE37EAB00CFEB2E /* Services */ = { + isa = XCSwiftPackageProductDependency; + productName = Services; + }; + 50816A1C2BC6E6DE00A43F3D /* NodeKit */ = { + isa = XCSwiftPackageProductDependency; + productName = NodeKit; + }; + 5097DF432BCD624700D422EE /* NodeKitMock */ = { + isa = XCSwiftPackageProductDependency; + productName = NodeKitMock; + }; + 50B5697F2BD183F00054DC09 /* Utils */ = { + isa = XCSwiftPackageProductDependency; + package = 50B5697E2BD183F00054DC09 /* XCRemoteSwiftPackageReference "iOS-Utils" */; + productName = Utils; + }; + 50B569822BD184150054DC09 /* Nuke */ = { + isa = XCSwiftPackageProductDependency; + package = 50B569812BD184150054DC09 /* XCRemoteSwiftPackageReference "Nuke" */; + productName = Nuke; + }; + 50B569842BD184AD0054DC09 /* NukeExtensions */ = { + isa = XCSwiftPackageProductDependency; + package = 50B569812BD184150054DC09 /* XCRemoteSwiftPackageReference "Nuke" */; + productName = NukeExtensions; + }; + 50B5698B2BD18F040054DC09 /* SnapKit */ = { + isa = XCSwiftPackageProductDependency; + package = 50B5698A2BD18F040054DC09 /* XCRemoteSwiftPackageReference "SnapKit" */; + productName = SnapKit; + }; + 50C445A12BC81FB300C515E6 /* SwiftMessages */ = { + isa = XCSwiftPackageProductDependency; + package = 50C445A02BC81FB300C515E6 /* XCRemoteSwiftPackageReference "SwiftMessages" */; + productName = SwiftMessages; + }; + 50C445A72BC8202700C515E6 /* ReactiveDataDisplayManager */ = { + isa = XCSwiftPackageProductDependency; + package = 50C445A62BC8202600C515E6 /* XCRemoteSwiftPackageReference "ReactiveDataDisplayManager" */; + productName = ReactiveDataDisplayManager; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = B96CD4411FDC5B1600F5A107 /* Project object */; } diff --git a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 6d2a51bb..919434a6 100644 --- a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme new file mode 100644 index 00000000..1c0ca6d0 --- /dev/null +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example.xcworkspace/contents.xcworkspacedata b/Example/Example.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a37cf193..00000000 --- a/Example/Example.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/Example/Example/Animals/AnimalCell.swift b/Example/Example/Animals/AnimalCell.swift deleted file mode 100644 index 577601d4..00000000 --- a/Example/Example/Animals/AnimalCell.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// AnimalCell.swift -// Example -// -// Created by Alexander Kravchenkov on 09.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation -import UIKit -import AlamofireImage - -public class AnimalCell: UITableViewCell { - - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var animalImage: UIImageView! - - func configure(name: String, url: String) { - self.titleLabel.text = name - self.animalImage.af_setImage(withURL: URL(string: url)!) - } - -} diff --git a/Example/Example/Animals/AnimalCell.xib b/Example/Example/Animals/AnimalCell.xib deleted file mode 100644 index 9882652a..00000000 --- a/Example/Example/Animals/AnimalCell.xib +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Example/Animals/AnimalCellGenerator.swift b/Example/Example/Animals/AnimalCellGenerator.swift deleted file mode 100644 index 0ac358bf..00000000 --- a/Example/Example/Animals/AnimalCellGenerator.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// AnimalCellGenerator.swift -// Example -// -// Created by Alexander Kravchenkov on 09.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation -import ReactiveDataDisplayManager - -class AnimalCellGenerator { - private let url: String - private let name: String - - public init(url: String, name: String) { - self.url = url - self.name = name - } -} - -extension AnimalCellGenerator: TableCellGenerator { - var identifier: UITableViewCell.Type { - return AnimalCell.self - } -} - -extension AnimalCellGenerator: ViewBuilder { - func build(view: AnimalCell) { - view.configure(name: self.name, url: self.url) - } -} diff --git a/Example/Example/Animals/AnimalPresenter.swift b/Example/Example/Animals/AnimalPresenter.swift deleted file mode 100644 index 734afd0d..00000000 --- a/Example/Example/Animals/AnimalPresenter.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// AnimalPresenter.swift -// Example -// -// Created by Alexander Kravchenkov on 09.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation -import CoreNetKit - -class AnimalPresernter { - var view: AnimalViewController? - - private var pagingContext: IteratableContext<[AnimalEntity]>? - - func loadAnimals() { - self.pagingContext = ListService().getIterator(with: 0, itemsOnPage: 10) - .onCompleted({ (entity) in - self.view?.add(models: entity) - }) - .onError({ (error) in - self.view?.showError(error) - }) - self.next() - } - - func next() { - guard self.pagingContext?.canMoveNext == true else { - return - } - self.pagingContext?.moveNext() - } -} diff --git a/Example/Example/Animals/AnimalViewController.swift b/Example/Example/Animals/AnimalViewController.swift deleted file mode 100644 index 0c485b48..00000000 --- a/Example/Example/Animals/AnimalViewController.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// AnimalViewController.swift -// Example -// -// Created by Alexander Kravchenkov on 09.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation -import UIKit -import ReactiveDataDisplayManager -import SwiftMessages - -class AnimalViewController: UIViewController { - - @IBOutlet weak var tableView: UITableView! - var adapter = BaseTableDataDisplayManager(estimatedHeight: 40) - var presenter = AnimalPresernter() - - override func viewDidLoad() { - super.viewDidLoad() - self.presenter.view = self - self.adapter.set(collection: self.tableView) - self.adapter.scrollViewWillEndDraggingEvent += { [weak self] velocity in - if velocity.y < 0 { - self?.adapter.clearCellGenerators() - self?.presenter.loadAnimals() - } else { - self?.presenter.next() - } - } - self.presenter.loadAnimals() - } - - func add(models: [AnimalEntity]) { - models.forEach { (entity) in - let generator = AnimalCellGenerator(url: entity.image, name: entity.name) - self.adapter.addCellGenerator(generator) - } - self.adapter.forceRefill() - } - - func showError(_ error: Error) { - let view = MessageView.viewFromNib(layout: .cardView) - view.configureTheme(.error) - view.configureDropShadow() - view.configureContent(title: "Error", body: error.localizedDescription, iconText: "😳") - SwiftMessages.show(view: view) - } -} diff --git a/Example/Example/AppDelegate.swift b/Example/Example/AppDelegate.swift index 6f1f4ed2..b4eeb5a7 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/AppDelegate.swift @@ -6,6 +6,7 @@ // Copyright © 2017 Кравченков Александр. All rights reserved. // +import MockServer import UIKit @UIApplicationMain @@ -13,9 +14,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + MockServer.start() + + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = LoginConfigurator().configure() + window?.makeKeyAndVisible() + return true } @@ -40,7 +48,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - - } - diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard deleted file mode 100644 index f8f87631..00000000 --- a/Example/Example/Base.lproj/Main.storyboard +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Example/Extensions/UIStoryboard+Extension.swift b/Example/Example/Extensions/UIStoryboard+Extension.swift new file mode 100644 index 00000000..7f04a94b --- /dev/null +++ b/Example/Example/Extensions/UIStoryboard+Extension.swift @@ -0,0 +1,19 @@ +// +// UIStoryboard+Extension.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import UIKit + +extension UIStoryboard { + static func instantiate( + ofType: ViewController.Type, + bundle: Bundle = .main + ) -> ViewController? { + let storyboard = UIStoryboard(name: String(describing: ViewController.self), bundle: bundle) + return storyboard.instantiateInitialViewController() as? ViewController + } +} diff --git a/Example/Example/Features/GroupFeature/GroupConfigurator.swift b/Example/Example/Features/GroupFeature/GroupConfigurator.swift new file mode 100644 index 00000000..678a73dd --- /dev/null +++ b/Example/Example/Features/GroupFeature/GroupConfigurator.swift @@ -0,0 +1,31 @@ +// +// GroupConfigurator.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import Services +import UIKit + +struct GroupConfigurator { + + // MARK: - Methods + + func configure() -> UIViewController { + guard + let viewController = UIStoryboard.instantiate(ofType: GroupViewController.self) + else { + fatalError("Can't load FeatureListViewController from storyboard") + } + + let presenter = GroupPresenter( + input: viewController, + viewModelProvider: GroupViewModelProvider(groupService: GroupService()) + ) + + viewController.output = presenter + return viewController + } +} diff --git a/Example/Example/Features/GroupFeature/GroupPresenter.swift b/Example/Example/Features/GroupFeature/GroupPresenter.swift new file mode 100644 index 00000000..260120a6 --- /dev/null +++ b/Example/Example/Features/GroupFeature/GroupPresenter.swift @@ -0,0 +1,60 @@ +// +// GroupPresenter.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import Models +import NodeKit +import Services + +protocol GroupViewOutput { + + @MainActor + func viewDidLoad() +} + +final class GroupPresenter { + + // MARK: - Private Properties + + private weak var input: GroupViewInput? + private let viewModelProvider: GroupViewModelProviderProtocol + + // MARK: - Initialization + + init(input: GroupViewInput, viewModelProvider: GroupViewModelProviderProtocol) { + self.input = input + self.viewModelProvider = viewModelProvider + } +} + +// MARK: - GroupViewOutput + +extension GroupPresenter: GroupViewOutput { + + @MainActor + func viewDidLoad() { + input?.showLoader() + start() + } +} + +// MARK: - Private Methods + +private extension GroupPresenter { + + func start() { + Task { + do { + let viewModel = try await viewModelProvider.provide() + await input?.hideLoader() + await input?.update(with: viewModel) + } catch { + await input?.show(error: error) + } + } + } +} diff --git a/Example/Example/Features/GroupFeature/GroupViewController.storyboard b/Example/Example/Features/GroupFeature/GroupViewController.storyboard new file mode 100644 index 00000000..d49770f1 --- /dev/null +++ b/Example/Example/Features/GroupFeature/GroupViewController.storyboard @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Features/GroupFeature/GroupViewController.swift b/Example/Example/Features/GroupFeature/GroupViewController.swift new file mode 100644 index 00000000..885f8187 --- /dev/null +++ b/Example/Example/Features/GroupFeature/GroupViewController.swift @@ -0,0 +1,77 @@ +// +// GroupViewController.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import NukeExtensions +import UIKit + +struct GroupViewModel { + let headerTitle: String + let headerImage: String + let bodyTitle: String + let bodyImage: String + let footerTitle: String + let footerImage: String +} + +@MainActor +protocol GroupViewInput: AnyObject, ErrorRepresentable { + func update(with model: GroupViewModel) + func showLoader() + func hideLoader() +} + +final class GroupViewController: UIViewController { + + // MARK: - Subviews + + @IBOutlet private weak var containerView: UIView! + @IBOutlet private weak var headerTitleLabel: UILabel! + @IBOutlet private weak var headerImageView: UIImageView! + @IBOutlet private weak var bodyTitleLabel: UILabel! + @IBOutlet private weak var bodyImageView: UIImageView! + @IBOutlet private weak var footerTitleLabel: UILabel! + @IBOutlet private weak var footerImageView: UIImageView! + @IBOutlet private weak var activityIndicator: UIActivityIndicatorView! + + // MARK: - Properties + + var output: GroupViewOutput? + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + output?.viewDidLoad() + } +} + +// MARK: - GroupViewInput + +extension GroupViewController: GroupViewInput { + + func update(with model: GroupViewModel) { + headerTitleLabel.text = model.headerTitle + bodyTitleLabel.text = model.bodyTitle + footerTitleLabel.text = model.footerTitle + loadImage(with: URL(string: model.headerImage)!, into: headerImageView) + loadImage(with: URL(string: model.bodyImage)!, into: bodyImageView) + loadImage(with: URL(string: model.footerImage)!, into: footerImageView) + } + + func showLoader() { + containerView.isHidden = true + activityIndicator.isHidden = false + activityIndicator.startAnimating() + } + + func hideLoader() { + containerView.isHidden = false + activityIndicator.isHidden = true + activityIndicator.stopAnimating() + } +} diff --git a/Example/Example/Features/GroupFeature/GroupViewModelProvider.swift b/Example/Example/Features/GroupFeature/GroupViewModelProvider.swift new file mode 100644 index 00000000..273083c3 --- /dev/null +++ b/Example/Example/Features/GroupFeature/GroupViewModelProvider.swift @@ -0,0 +1,76 @@ +// +// GroupViewModelProvider.swift +// Example +// +// Created by Andrei Frolov on 16.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import Foundation +import Models +import NodeKit +import Services + +protocol GroupViewModelProviderProtocol: Actor { + func provide() async throws -> GroupViewModel +} + +actor GroupViewModelProvider: GroupViewModelProviderProtocol { + + // MARK: - Private Properties + + private var tasks: [CancellableTask] = [] + private let groupService: GroupServiceProtocol + + // MARK: - Initialization + + init(groupService: GroupServiceProtocol) { + self.groupService = groupService + } + + // MARK: - GroupViewModelServiceProtocol + + func provide() async throws -> GroupViewModel { + cancelAllTasks() + + let headerTask = storedTask { await $0.header() } + let bodyTask = storedTask { await $0.body() } + let footerTask = storedTask { await $0.footer() } + + async let header = headerTask.value + async let body = bodyTask.value + async let footer = footerTask.value + + return try await GroupViewModel( + headerTitle: header.text, + headerImage: header.image, + bodyTitle: body.text, + bodyImage: body.image, + footerTitle: footer.text, + footerImage: footer.image + ) + } +} + +// MARK: - Private Methods + +private extension GroupViewModelProvider { + + func storedTask(_ nodeResult: @escaping (GroupServiceProtocol) async -> NodeResult) -> Task { + let task = Task { + try await nodeResult(groupService) + .mapError { + cancelAllTasks() + return $0 + } + .get() + } + tasks.append(task) + return task + } + + func cancelAllTasks() { + tasks.forEach { $0.cancel() } + tasks.removeAll() + } +} diff --git a/Example/Example/Features/PaginationFeature/Cells/BaseLoadingSubview.swift b/Example/Example/Features/PaginationFeature/Cells/BaseLoadingSubview.swift new file mode 100644 index 00000000..0a97f375 --- /dev/null +++ b/Example/Example/Features/PaginationFeature/Cells/BaseLoadingSubview.swift @@ -0,0 +1,30 @@ +// +// BaseLoadingSubview.swift +// Example +// +// Created by Andrei Frolov on 18.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import UIKit +import Utils + +struct BaseLoadingSubviewModel { + let height: CGFloat + let cornerRadius: CGFloat +} + +final class BaseLoadingSubview: UIView, LoadingSubview, LoadingSubviewConfigurable { + typealias Model = BaseLoadingSubviewModel + + var height: CGFloat = 0 + + func configure(color: UIColor) { + backgroundColor = color + } + + func configure(model: BaseLoadingSubviewModel) { + height = model.height + layer.cornerRadius = model.cornerRadius + } +} diff --git a/Example/Example/Features/PaginationFeature/Cells/PaginationCell.swift b/Example/Example/Features/PaginationFeature/Cells/PaginationCell.swift new file mode 100644 index 00000000..c5acdd7e --- /dev/null +++ b/Example/Example/Features/PaginationFeature/Cells/PaginationCell.swift @@ -0,0 +1,87 @@ +// +// PaginationCell.swift +// Example +// +// Created by Alexander Kravchenkov on 09.04.2018. +// Copyright © 2018 Кравченков Александр. All rights reserved. +// + +import Foundation +import NukeExtensions +import ReactiveDataDisplayManager +import UIKit +import Utils + +final class PaginationCell: UITableViewCell, ConfigurableItem { + + // MARK: - Constants + + private enum Constants { + static let cornerRadius: CGFloat = 16 + static let imageTransitionDuration: TimeInterval = 0.3 + } + + // MARK: - Subviews + + @IBOutlet private weak var icon: UIImageView! + @IBOutlet private weak var shimmerView: BaseLoadingView! + @IBOutlet private weak var titleLabel: UILabel! + + // MARK: - Lifecycle + + override func layoutSubviews() { + super.layoutSubviews() + icon.layer.cornerRadius = Constants.cornerRadius + shimmerView.layer.cornerRadius = Constants.cornerRadius + } + + override func prepareForReuse() { + super.prepareForReuse() + icon.image = nil + } + + override func awakeFromNib() { + super.awakeFromNib() + configureShimmerView() + } + + // MARK: - Methods + + func configure(with model: PaginationCellViewModel) { + startShimmer() + titleLabel.text = model.name + loadImage(with: URL(string: model.url)!, into: icon, completion: { [weak self] _ in + self?.stopShimmer() + }) + } +} + +// MARK: - Private Methods + +private extension PaginationCell { + + func configureShimmerView() { + shimmerView.configure(blocks: makeShimmerBlocks(), config: makeShimmerConfig()) + } + + func makeShimmerBlocks() -> [LoadingViewBlock] { + let model = BaseLoadingSubviewModel(height: icon.frame.height, cornerRadius: Constants.cornerRadius) + return [ + BaseLoadingViewBlock(model: model) + ] + } + + func makeShimmerConfig() -> LoadingViewConfig { + return LoadingViewConfig (placeholderColor: .gray.withAlphaComponent(0.2)) + } + + func startShimmer() { + shimmerView.setNeedAnimating(true) + shimmerView.isHidden = false + } + + func stopShimmer() { + shimmerView.setNeedAnimating(false) + shimmerView.isHidden = true + } +} diff --git a/Example/Example/Features/PaginationFeature/Cells/PaginationCell.xib b/Example/Example/Features/PaginationFeature/Cells/PaginationCell.xib new file mode 100644 index 00000000..3a41908e --- /dev/null +++ b/Example/Example/Features/PaginationFeature/Cells/PaginationCell.xib @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Features/PaginationFeature/Cells/PaginationCellViewModel.swift b/Example/Example/Features/PaginationFeature/Cells/PaginationCellViewModel.swift new file mode 100644 index 00000000..6704c499 --- /dev/null +++ b/Example/Example/Features/PaginationFeature/Cells/PaginationCellViewModel.swift @@ -0,0 +1,14 @@ +// +// PaginationCellViewModel.swift +// Example +// +// Created by Alexander Kravchenkov on 09.04.2018. +// Copyright © 2018 Кравченков Александр. All rights reserved. +// + +import UIKit + +struct PaginationCellViewModel { + let name: String + let url: String +} diff --git a/Example/Example/Features/PaginationFeature/PaginationConfigurator.swift b/Example/Example/Features/PaginationFeature/PaginationConfigurator.swift new file mode 100644 index 00000000..c8eb1a21 --- /dev/null +++ b/Example/Example/Features/PaginationFeature/PaginationConfigurator.swift @@ -0,0 +1,44 @@ +// +// PaginationConfigurator.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import NodeKit +import Services +import UIKit + +struct PaginationConfigurator { + + // MARK: - Constants + + private enum Constants { + static let pageSize: Int = 15 + } + + // MARK: - Methods + + func configure() -> UIViewController { + guard + let viewController = UIStoryboard.instantiate(ofType: PaginationViewController.self) + else { + fatalError("Can't load PaginationViewController from storyboard") + } + + let router = PaginationRouter() + let presenter = PaginationPresenter( + input: viewController, + router: router, + iterator: AsyncPagerIterator( + dataProvider: PaginationContentDataProvider(), + pageSize: Constants.pageSize + ) + ) + + viewController.output = presenter + + return viewController + } +} diff --git a/Example/Example/Features/PaginationFeature/PaginationPresenter.swift b/Example/Example/Features/PaginationFeature/PaginationPresenter.swift new file mode 100644 index 00000000..1f62f65d --- /dev/null +++ b/Example/Example/Features/PaginationFeature/PaginationPresenter.swift @@ -0,0 +1,99 @@ +// +// PaginationPresenter.swift +// Example +// +// Created by Alexander Kravchenkov on 09.04.2018. +// Copyright © 2018 Кравченков Александр. All rights reserved. +// + +import Foundation +import Models +import NodeKit +import ReactiveDataDisplayManager + +protocol PaginationViewOutput { + func viewDidLoad() + func nextPageRequested() + func refreshDidRequest() +} + +class PaginationPresenter { + + // MARK: - Private Properties + + private weak var input: PaginationViewInput? + private let router: PaginationRouterInput + private let iterator: any AsyncIterator<[PaginationResponseEntity]> + + // MARK: - Initialization + + init( + input: PaginationViewInput, + router: PaginationRouterInput, + iterator: some AsyncIterator<[PaginationResponseEntity]> + ) { + self.input = input + self.router = router + self.iterator = iterator + } +} + +// MARK: - PaginationViewOutput + +extension PaginationPresenter: PaginationViewOutput { + + func viewDidLoad() { + startLoad() + } + + func nextPageRequested() { + Task { + await input?.showPaginationLoading() + if let generators = await next()?.value { + await input?.add(generators: generators) + } + await input?.hidePaginationLoading() + } + } + + func refreshDidRequest() { + Task { + await iterator.renew() + startLoad() + } + } +} + +// MARK: - Private Methods + +private extension PaginationPresenter { + + func startLoad() { + Task { + if let generators = await next()?.value { + await input?.update(with: generators) + } + } + } + + func next() async -> Result<[TableCellGenerator], Error>? { + guard await iterator.hasNext() else { + await input?.disablePagination() + return nil + } + + await input?.enablePagination() + + return await iterator.next() + .map { models in + return models.map { + let viewModel = PaginationCellViewModel(name: $0.name, url: $0.image) + return PaginationCell.rddm.baseGenerator(with: viewModel) + } + } + .asyncFlatMapError { + await router.show(error: $0) + return .failure($0) + } + } +} diff --git a/Example/Example/Features/PaginationFeature/PaginationRouter.swift b/Example/Example/Features/PaginationFeature/PaginationRouter.swift new file mode 100644 index 00000000..1474fe44 --- /dev/null +++ b/Example/Example/Features/PaginationFeature/PaginationRouter.swift @@ -0,0 +1,11 @@ +// +// PaginationRouter.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +protocol PaginationRouterInput: ErrorRepresentable { } + +struct PaginationRouter: PaginationRouterInput { } diff --git a/Example/Example/Features/PaginationFeature/PaginationViewController.storyboard b/Example/Example/Features/PaginationFeature/PaginationViewController.storyboard new file mode 100644 index 00000000..868db3dd --- /dev/null +++ b/Example/Example/Features/PaginationFeature/PaginationViewController.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Features/PaginationFeature/PaginationViewController.swift b/Example/Example/Features/PaginationFeature/PaginationViewController.swift new file mode 100644 index 00000000..c70005b6 --- /dev/null +++ b/Example/Example/Features/PaginationFeature/PaginationViewController.swift @@ -0,0 +1,121 @@ +// +// PaginationViewController.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import ReactiveDataDisplayManager +import UIKit + +@MainActor +protocol PaginationViewInput: AnyObject { + func update(with generators: [TableCellGenerator]) + func add(generators: [TableCellGenerator]) + func disablePagination() + func enablePagination() + func showPaginationLoading() + func hidePaginationLoading() +} + +final class PaginationViewController: UIViewController { + + // MARK: - Subviews + + @IBOutlet private weak var tableView: UITableView! + + private let loadingView = PaginationLoadingView() + private let refreshControl = UIRefreshControl() + + // MARK: - Private Properties + + private lazy var paginationPlugin: TablePaginatablePlugin = .paginatable(progressView: loadingView, output: self) + private lazy var tableManager = tableView + .rddm + .baseBuilder + .add(plugin: paginationPlugin) + .build() + + // MARK: - Properties + + var output: PaginationViewOutput? + + // MARK: - Lifcycle + + override func viewDidLoad() { + super.viewDidLoad() + configure() + output?.viewDidLoad() + } +} + +// MARK: - Private Methods + +private extension PaginationViewController { + + func configure() { + configureTableView() + configureRefreshControl() + } + + func configureTableView() { + tableView.showsVerticalScrollIndicator = false + tableView.refreshControl = refreshControl + } + + func configureRefreshControl() { + refreshControl.addTarget(self, action: #selector(refreshDidRequest), for: .valueChanged) + } + + @objc + func refreshDidRequest() { + output?.refreshDidRequest() + } +} + +// MARK: - PaginationViewInput + +extension PaginationViewController: PaginationViewInput { + + func update(with generators: [TableCellGenerator]) { + refreshControl.endRefreshing() + tableManager.clearCellGenerators() + tableManager.addCellGenerators(generators) + tableManager.forceRefill() + } + + func add(generators: [TableCellGenerator]) { + tableManager.addCellGenerators(generators) + tableManager.forceRefill() + } + + func disablePagination() { + paginationPlugin.updatePagination(canIterate: false) + } + + func enablePagination() { + paginationPlugin.updatePagination(canIterate: true) + } + + func showPaginationLoading() { + paginationPlugin.updateProgress(isLoading: true) + } + + func hidePaginationLoading() { + paginationPlugin.updateProgress(isLoading: false) + } +} + +// MARK: - PaginatableOutput + +extension PaginationViewController: PaginatableOutput { + + func onPaginationInitialized(with input: ReactiveDataDisplayManager.PaginatableInput) { + input.updatePagination(canIterate: true) + } + + func loadNextPage(with input: ReactiveDataDisplayManager.PaginatableInput) { + output?.nextPageRequested() + } +} diff --git a/Example/Example/Features/PaginationFeature/Subviews/PaginationLoadingView.swift b/Example/Example/Features/PaginationFeature/Subviews/PaginationLoadingView.swift new file mode 100644 index 00000000..7fefa727 --- /dev/null +++ b/Example/Example/Features/PaginationFeature/Subviews/PaginationLoadingView.swift @@ -0,0 +1,68 @@ +// +// PaginationLoadingView.swift +// Example +// +// Created by Andrei Frolov on 11.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import ReactiveDataDisplayManager +import SnapKit +import UIKit + +final class PaginationLoadingView: UIView, ProgressDisplayableItem { + + // MARK: - Constants + + private enum Constants { + static let activityIndicatorSize: CGFloat = 30 + } + + // MARK: - Subviews + + private let activityIndicator = UIActivityIndicatorView() + + // MARK: - Lifecycle + + override init(frame: CGRect) { + super.init(frame: frame) + configureAcitvityIndicator() + self.frame.size.height = Constants.activityIndicatorSize + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configureAcitvityIndicator() + } + + override func awakeFromNib() { + super.awakeFromNib() + configureAcitvityIndicator() + } + + // MARK: - ProgressDisplayableItem + + func showProgress(_ isLoading: Bool) { + if isLoading { + activityIndicator.startAnimating() + } else { + activityIndicator.stopAnimating() + } + } + + func showError(_ error: (Error)?) { } + func setOnRetry(action: @escaping () -> Void) { } +} + +// MARK: - Private Methods + +private extension PaginationLoadingView { + + func configureAcitvityIndicator() { + addSubview(activityIndicator) + activityIndicator.snp.makeConstraints { + $0.centerX.centerY.equalToSuperview() + $0.width.height.equalTo(Constants.activityIndicatorSize) + } + } +} diff --git a/Example/Example/Flows/AuthFlow/Credentials.swift b/Example/Example/Flows/AuthFlow/Credentials.swift new file mode 100644 index 00000000..ce9a244c --- /dev/null +++ b/Example/Example/Flows/AuthFlow/Credentials.swift @@ -0,0 +1,23 @@ +// +// Credentials.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +struct Credentials { + let email: String + let password: String +} + +extension Credentials { + + init?(email: String?, password: String?) { + guard let email, let password else { + return nil + } + self.email = email + self.password = password + } +} diff --git a/Example/Example/Flows/AuthFlow/LoginConfigurator.swift b/Example/Example/Flows/AuthFlow/LoginConfigurator.swift new file mode 100644 index 00000000..35a46c53 --- /dev/null +++ b/Example/Example/Flows/AuthFlow/LoginConfigurator.swift @@ -0,0 +1,30 @@ +// +// LoginConfigurator.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import Services +import UIKit + +struct LoginConfigurator { + + // MARK: - Methods + + func configure() -> UIViewController { + guard + let viewController = UIStoryboard.instantiate(ofType: LoginViewController.self) + else { + fatalError("Can't load LoginViewController from storyboard") + } + + let router = LoginRouter(viewController: viewController) + let presenter = LoginPresenter(router: router, service: AuthService()) + + viewController.output = presenter + + return UINavigationController(rootViewController: viewController) + } +} diff --git a/Example/Example/Flows/AuthFlow/LoginPresenter.swift b/Example/Example/Flows/AuthFlow/LoginPresenter.swift new file mode 100644 index 00000000..82f3923a --- /dev/null +++ b/Example/Example/Flows/AuthFlow/LoginPresenter.swift @@ -0,0 +1,57 @@ +// +// LoginPresenter.swift +// Example +// +// Created by Александр Кравченков on 09.12.2017. +// Copyright © 2017 Кравченков Александр. All rights reserved. +// + +import Foundation +import Services + +protocol LoginViewOutput { + func credentialsDidReceive(credentials: Credentials?) +} + +public class LoginPresenter { + + // MARK: - Private Properties + + private let router: LoginRouterInput + private let service: AuthServiceProtocol + + // MARK: - Initialization + + init(router: LoginRouterInput, service: AuthServiceProtocol) { + self.router = router + self.service = service + } +} + +// MARK: - LoginViewOutput + +extension LoginPresenter: LoginViewOutput { + + func credentialsDidReceive(credentials: Credentials?) { + guard let credentials else { + return + } + + Task { await auth(with: credentials) } + } +} + +// MARK: - Private Methods + +private extension LoginPresenter { + + private func auth(with credentials: Credentials) async { + let result = await service.auth(by: credentials.email, and: credentials.password) + switch result { + case .success: + await router.showFeatureList() + case .failure(let error): + await router.show(error: error) + } + } +} diff --git a/Example/Example/Flows/AuthFlow/LoginRouter.swift b/Example/Example/Flows/AuthFlow/LoginRouter.swift new file mode 100644 index 00000000..a05e4eaf --- /dev/null +++ b/Example/Example/Flows/AuthFlow/LoginRouter.swift @@ -0,0 +1,39 @@ +// +// LoginRouter.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import UIKit + +protocol LoginRouterInput: ErrorRepresentable { + + @MainActor + func showFeatureList() +} + +struct LoginRouter { + + // MARK: - Properties + + private let viewController: UIViewController + + // MARK: - Initialization + + init(viewController: UIViewController) { + self.viewController = viewController + } +} + +// MARK: - LoginRouterInput + +extension LoginRouter: LoginRouterInput { + + @MainActor + func showFeatureList() { + let featureListViewController = FeatureListConfigurator().configure() + viewController.navigationController?.viewControllers = [featureListViewController] + } +} diff --git a/Example/Example/Flows/AuthFlow/LoginViewController.storyboard b/Example/Example/Flows/AuthFlow/LoginViewController.storyboard new file mode 100644 index 00000000..ec915796 --- /dev/null +++ b/Example/Example/Flows/AuthFlow/LoginViewController.storyboard @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Flows/AuthFlow/LoginViewController.swift b/Example/Example/Flows/AuthFlow/LoginViewController.swift new file mode 100644 index 00000000..db35419b --- /dev/null +++ b/Example/Example/Flows/AuthFlow/LoginViewController.swift @@ -0,0 +1,123 @@ +// +// ViewController.swift +// Example +// +// Created by Александр Кравченков on 09.12.2017. +// Copyright © 2017 Кравченков Александр. All rights reserved. +// + +import UIKit +import SwiftMessages + +class LoginViewController: UIViewController { + + // MARK: - Subviews + + @IBOutlet private weak var loginCartView: UIView! + @IBOutlet private weak var loginCartLoginActionButton: UIButton! + @IBOutlet private weak var loginCartYConstraint: NSLayoutConstraint! + @IBOutlet private weak var loginCartImage: UIImageView! + @IBOutlet private weak var loginCartEmailTextField: UITextField! + @IBOutlet private weak var loginCartPasswordTextField: UITextField! + + // MARK: - Properties + + var output: LoginViewOutput? + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + self.configure() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.removeSubscriptions() + } +} + +// MARK: - IBActions + +extension LoginViewController { + + @IBAction + func actionLoginButtonTouchUpInside(_ sender: Any) { + let credentials = Credentials( + email: loginCartEmailTextField.text, + password: loginCartPasswordTextField.text + ) + output?.credentialsDidReceive(credentials: credentials) + } +} + +// MARK: - Notifications + +private extension LoginViewController { + + func subscribeOnNotifications() { + NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: UIResponder.keyboardDidShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.keyBoardDidHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil) + } + + func removeSubscriptions() { + NotificationCenter.default.removeObserver(self) + } + + @objc + func keyboardDidShow(_ notification: NSNotification) { + guard let userInfo = notification.userInfo, + let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, + self.loginCartView.frame.maxY > keyboardRect.minY else { + return + } + + loginCartYConstraint.constant -= self.loginCartView.frame.maxY - keyboardRect.minY + + UIView.animate(withDuration: 0.5) { + self.view.layoutIfNeeded() + self.loginCartView.layoutIfNeeded() + } + } + + @objc + func keyBoardDidHide(_ notification: NSNotification) { + UIView.animate(withDuration: 0.2) { + self.loginCartYConstraint.constant = 0 + self.loginCartView.layoutIfNeeded() + } + } +} + +// MARK: - Configuration + +private extension LoginViewController { + + func configure() { + configureUI() + subscribeOnNotifications() + } + + func configureUI() { + configureImage() + configureTextFields() + configureLoginButton() + } + + func configureLoginButton() { + loginCartLoginActionButton.clipsToBounds = true + loginCartLoginActionButton.layer.cornerRadius = 5 + } + + func configureImage() { + loginCartImage.layer.cornerRadius = 20 + loginCartImage.clipsToBounds = true + } + + func configureTextFields() { + loginCartEmailTextField.keyboardType = .emailAddress + loginCartEmailTextField.placeholder = "Your email" + + loginCartPasswordTextField.placeholder = "Your password" + } +} diff --git a/Example/Example/Flows/FeatureListFlow/Cells/FeatureCell.swift b/Example/Example/Flows/FeatureListFlow/Cells/FeatureCell.swift new file mode 100644 index 00000000..d3d6ca33 --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/Cells/FeatureCell.swift @@ -0,0 +1,102 @@ +// +// FeatureCell.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import ReactiveDataDisplayManager +import UIKit + +final class FeatureCell: UITableViewCell, ConfigurableItem { + + // MARK: - Constants + + private enum Constants { + static var cornerRadius: CGFloat = 16 + static var shadowOpacity: Float = 0.3 + static var shadowOffset: CGSize = CGSize(width: 3, height: 3) + static var shadowRadius: CGFloat = 3 + static var pressStateScale: CGFloat = 0.95 + } + + // MARK: - Subviews + + @IBOutlet private weak var contentContainerView: UIView! + @IBOutlet private weak var titleLabel: UILabel! + + // MARK: - Private Properties + + private var model: FeatureCellViewModel? + + // MARK: - Lifecycle + + override func awakeFromNib() { + super.awakeFromNib() + configureGesture() + } + + override func layoutSubviews() { + super.layoutSubviews() + contentContainerView.layer.shadowColor = UIColor.black.cgColor + contentContainerView.layer.shadowOpacity = Constants.shadowOpacity + contentContainerView.layer.shadowOffset = Constants.shadowOffset + contentContainerView.layer.shadowRadius = Constants.shadowRadius + contentContainerView.layer.cornerRadius = Constants.cornerRadius + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + updatePressState(isActive: true) + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + updatePressState(isActive: false) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + updatePressState(isActive: false) + } + + // MARK: - Methods + + func configure(with model: FeatureCellViewModel) { + titleLabel.text = model.title + self.model = model + } +} + +// MARK: - Private Methods + +private extension FeatureCell { + + func configureGesture() { + let recognizer = UITapGestureRecognizer() + recognizer.addTarget(self, action: #selector(tapAction)) + contentContainerView.addGestureRecognizer(recognizer) + } + + func updatePressState(isActive: Bool) { + UIView.animate(withDuration: 0.1, animations: { + guard isActive else { + self.contentContainerView.transform = .identity + return + } + let scale = Constants.pressStateScale + self.contentContainerView.transform = .init(scaleX: scale, y: scale) + }) + } +} + +// MARK: - Actions + +private extension FeatureCell { + + @objc + func tapAction(_ sender: UITapGestureRecognizer) { + model?.didTap?() + } +} diff --git a/Example/Example/Flows/FeatureListFlow/Cells/FeatureCell.xib b/Example/Example/Flows/FeatureListFlow/Cells/FeatureCell.xib new file mode 100644 index 00000000..72660dbd --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/Cells/FeatureCell.xib @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Flows/FeatureListFlow/Cells/FeatureCellViewModel.swift b/Example/Example/Flows/FeatureListFlow/Cells/FeatureCellViewModel.swift new file mode 100644 index 00000000..ec6e7331 --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/Cells/FeatureCellViewModel.swift @@ -0,0 +1,23 @@ +// +// FeatureCellViewModel.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import UIKit + +final class FeatureCellViewModel { + + // MARK: - Properties + + let title: String + var didTap: (@MainActor () -> Void)? + + // MARK: - Initialization + + init(_ title: String) { + self.title = title + } +} diff --git a/Example/Example/Flows/FeatureListFlow/FeatureListConfigurator.swift b/Example/Example/Flows/FeatureListFlow/FeatureListConfigurator.swift new file mode 100644 index 00000000..d5c86d0a --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/FeatureListConfigurator.swift @@ -0,0 +1,29 @@ +// +// FeatureListConfigurator.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import UIKit + +struct FeatureListConfigurator { + + // MARK: - Methods + + func configure() -> UIViewController { + guard + let viewController = UIStoryboard.instantiate(ofType: FeatureListViewController.self) + else { + fatalError("Can't load FeatureListViewController from storyboard") + } + + let router = FeatureListRouter(viewController: viewController) + let presenter = FeatureListPresenter(input: viewController, router: router) + + viewController.output = presenter + + return viewController + } +} diff --git a/Example/Example/Flows/FeatureListFlow/FeatureListPresenter.swift b/Example/Example/Flows/FeatureListFlow/FeatureListPresenter.swift new file mode 100644 index 00000000..46ba273c --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/FeatureListPresenter.swift @@ -0,0 +1,65 @@ +// +// FeatureListPresenter.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import ReactiveDataDisplayManager + +protocol FeatureListViewOutput { + + func viewDidLoad() +} + +final class FeatureListPresenter { + + // MARK: - Private Properties + + private weak var input: FeatureListViewInput? + private let router: FeatureListRouterInput + + // MARK: - Initialization + + init(input: FeatureListViewInput, router: FeatureListRouterInput) { + self.input = input + self.router = router + } +} + +// MARK: - FeatureListViewOutput + +extension FeatureListPresenter: FeatureListViewOutput { + + func viewDidLoad() { + input?.update(with: makeGenerators()) + } +} + + +// MARK: - Private Methods + +private extension FeatureListPresenter { + + func makeGenerators() -> [TableCellGenerator] { + return [ + makePaginationViewModel(), + makeGroupOfRequestsViewModel() + ].map { + FeatureCell.rddm.baseGenerator(with: $00) + } + } + + func makePaginationViewModel() -> FeatureCellViewModel { + let viewModel = FeatureCellViewModel("Pagination") + viewModel.didTap = { [weak self] in self?.router.showPagination() } + return viewModel + } + + func makeGroupOfRequestsViewModel() -> FeatureCellViewModel { + let viewModel = FeatureCellViewModel("Group of requests") + viewModel.didTap = { [weak self] in self?.router.showGroup() } + return viewModel + } +} diff --git a/Example/Example/Flows/FeatureListFlow/FeatureListRouter.swift b/Example/Example/Flows/FeatureListFlow/FeatureListRouter.swift new file mode 100644 index 00000000..e035251f --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/FeatureListRouter.swift @@ -0,0 +1,50 @@ +// +// FeatureListRouter.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import UIKit + +protocol FeatureListRouterInput { + + @MainActor + func showPagination() + + @MainActor + func showGroup() +} + +struct FeatureListRouter { + + // MARK: - Private Properties + + private weak var viewController: UIViewController? + + // MARK: - Initialization + + init(viewController: UIViewController) { + self.viewController = viewController + } +} + +// MARK: - FeatureListRouterInput + +extension FeatureListRouter: FeatureListRouterInput { + + @MainActor + func showPagination() { + let paginationViewController = PaginationConfigurator().configure() + paginationViewController.navigationItem.largeTitleDisplayMode = .never + viewController?.navigationController?.pushViewController(paginationViewController, animated: true) + } + + @MainActor + func showGroup() { + let groupViewController = GroupConfigurator().configure() + groupViewController.navigationItem.largeTitleDisplayMode = .never + viewController?.navigationController?.pushViewController(groupViewController, animated: true) + } +} diff --git a/Example/Example/Flows/FeatureListFlow/FeatureListViewController.storyboard b/Example/Example/Flows/FeatureListFlow/FeatureListViewController.storyboard new file mode 100644 index 00000000..0524739b --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/FeatureListViewController.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Flows/FeatureListFlow/FeatureListViewController.swift b/Example/Example/Flows/FeatureListFlow/FeatureListViewController.swift new file mode 100644 index 00000000..096b2422 --- /dev/null +++ b/Example/Example/Flows/FeatureListFlow/FeatureListViewController.swift @@ -0,0 +1,88 @@ +// +// FeatureListViewController.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import ReactiveDataDisplayManager +import UIKit + +protocol FeatureListViewInput: AnyObject { + func update(with generators: [TableCellGenerator]) +} + +final class FeatureListViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let title = "Features" + static let contentOffset: CGPoint = CGPoint(x: .zero, y: -60) + } + + // MARK: - Subivews + + @IBOutlet private weak var tableView: UITableView! + + // MARK: - Properties + + var output: FeatureListViewOutput? + + // MARK: - Private Properties + + private lazy var tableManager = tableView + .rddm + .baseBuilder + .build() + + // MARK: - Lifecycle + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + configureNavigationBar() + } + + override func viewDidLoad() { + super.viewDidLoad() + configure() + output?.viewDidLoad() + } +} + +// MARK: - FeatureListViewInput + +extension FeatureListViewController: FeatureListViewInput { + + func update(with generators: [TableCellGenerator]) { + tableManager.clearCellGenerators() + tableManager.addCellGenerators(generators) + tableManager.forceRefill() + } +} + +// MARK: - Private Methods + +private extension FeatureListViewController { + + func configure() { + configureTableView() + configureTitle() + } + + func configureTableView() { + tableView.contentOffset = Constants.contentOffset + tableView.showsVerticalScrollIndicator = false + } + + func configureTitle() { + title = Constants.title + } + + func configureNavigationBar() { + navigationItem.largeTitleDisplayMode = .always + navigationController?.navigationBar.prefersLargeTitles = true + navigationController?.navigationBar.tintColor = .black + } +} diff --git a/Example/Example/Info.plist b/Example/Example/Info.plist index 8ff5153d..34f94d37 100644 --- a/Example/Example/Info.plist +++ b/Example/Example/Info.plist @@ -2,11 +2,6 @@ - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -25,10 +20,13 @@ 1 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/Example/Example/Login/LoginPresenter.swift b/Example/Example/Login/LoginPresenter.swift deleted file mode 100644 index 33f17e93..00000000 --- a/Example/Example/Login/LoginPresenter.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// LoginPresenter.swift -// Example -// -// Created by Александр Кравченков on 09.12.2017. -// Copyright © 2017 Кравченков Александр. All rights reserved. -// - -import Foundation - -public struct LoginViewModel { - let email: String - let password: String -} - -public class LoginPresenter { - - var view: LoginViewController? - - public func login(email: String?, password: String?) { - guard let guardedEmail = email, let guardedPassword = password else { - return - } - - AuthService().auth(by: guardedEmail, and: guardedPassword) - .onCompleted { [weak self] in - self?.view?.authComplete() - } - .onError { [weak self] (error) in - self?.view?.showError(error) - } - } -} diff --git a/Example/Example/Login/LoginViewController.swift b/Example/Example/Login/LoginViewController.swift deleted file mode 100644 index a7d81179..00000000 --- a/Example/Example/Login/LoginViewController.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// ViewController.swift -// Example -// -// Created by Александр Кравченков on 09.12.2017. -// Copyright © 2017 Кравченков Александр. All rights reserved. -// - -import UIKit -import SwiftMessages - -class LoginViewController: UIViewController { - - // MARK: - IBOutlets - - @IBOutlet - fileprivate weak var loginCartView: UIView! - - @IBOutlet - weak var loginCartLoginActionButton: UIButton! - - @IBOutlet - fileprivate weak var loginCartYConstraint: NSLayoutConstraint! - - @IBOutlet - fileprivate weak var loginCartImage: UIImageView! - - @IBOutlet - fileprivate weak var loginCartEmailTextField: UITextField! - - @IBOutlet - fileprivate weak var loginCartPasswordTextField: UITextField! - - // MARK: - Properties - - var presenter: LoginPresenter? - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - self.configure() - self.presenter = LoginPresenter() - self.presenter?.view = self - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - self.removeSubscriptions() - } - - func authComplete() { - let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) - let vc = storyboard.instantiateViewController(withIdentifier: "AnimalViewController") as! AnimalViewController - self.show(vc, sender: self) - } - - func showError(_ error: Error) { - let view = MessageView.viewFromNib(layout: .cardView) - view.configureTheme(.error) - view.configureDropShadow() - view.configureContent(title: "Error", body: error.localizedDescription, iconText: "😳") - SwiftMessages.show(view: view) - } -} - -// MARK: - IBActions - -extension LoginViewController { - - @IBAction - func actionLoginButtonTouchUpInside(_ sender: Any) { - self.presenter?.login(email: self.loginCartEmailTextField.text, password: self.loginCartPasswordTextField.text) - } -} - -// MARK: - Notifications - -private extension LoginViewController { - - func subscribeOnNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.keyBoardDidHide(_:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil) - } - - func removeSubscriptions() { - NotificationCenter.default.removeObserver(self) - } - - @objc - func keyboardDidShow(_ notification: NSNotification) { - guard let userInfo = notification.userInfo, - let keyboardRect = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect, - self.loginCartView.frame.maxY > keyboardRect.minY else { - return - } - - self.loginCartYConstraint.constant -= self.loginCartView.frame.maxY - keyboardRect.minY - - UIView.animate(withDuration: 0.5) { - self.view.layoutIfNeeded() - self.loginCartView.layoutIfNeeded() - } - } - - @objc - func keyBoardDidHide(_ notification: NSNotification) { - UIView.animate(withDuration: 0.2) { - self.loginCartYConstraint.constant = 0 - self.loginCartView.layoutIfNeeded() - } - } -} - -// MARK: - Configuration - -private extension LoginViewController { - - func configure() { - self.configureUI() - self.subscribeOnNotifications() - } - - func configureUI() { - self.configureImage() - self.configureTextFields() - self.configureLoginButton() - } - - func configureLoginButton() { - self.loginCartLoginActionButton.clipsToBounds = true - self.loginCartLoginActionButton.layer.cornerRadius = 5 - } - - func configureImage() { - self.loginCartImage.layer.cornerRadius = 20 - self.loginCartImage.clipsToBounds = true - } - - func configureTextFields() { - self.loginCartEmailTextField.keyboardType = .emailAddress - self.loginCartEmailTextField.placeholder = "Your email" - - self.loginCartPasswordTextField.placeholder = "Your password" - } -} diff --git a/Example/Example/Models/AnimalEntity.swift b/Example/Example/Models/AnimalEntity.swift deleted file mode 100644 index bc98a4a9..00000000 --- a/Example/Example/Models/AnimalEntity.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// AnimalEntity.swift -// Example -// -// Created by Alexander Kravchenkov on 06.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation - -public struct AnimalEntity { - var name: String - var image: String - - init?(json: [String: Any]) { - guard let name = json["name"] as? String, let image = json["img"] as? String else { - return nil - } - - self.name = name - self.image = image - } -} diff --git a/Example/Example/Models/AuthTokenEntity.swift b/Example/Example/Models/AuthTokenEntity.swift deleted file mode 100644 index a9e967e4..00000000 --- a/Example/Example/Models/AuthTokenEntity.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// AuthTokenEntity.swift -// Example -// -// Created by Alexander Kravchenkov on 05.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation - -struct AuthTokenEntity { - let accessToken: String - let refreshtoken: String -} diff --git a/Example/Example/Models/AuthTokenModel.swift b/Example/Example/Models/AuthTokenModel.swift deleted file mode 100644 index aa0d3c6a..00000000 --- a/Example/Example/Models/AuthTokenModel.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// AuthTokenModel.swift -// Example -// -// Created by Alexander Kravchenkov on 05.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation diff --git a/Example/Example/Models/UserEntity.swift b/Example/Example/Models/UserEntity.swift deleted file mode 100644 index 09063a84..00000000 --- a/Example/Example/Models/UserEntity.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// UserEntity.swift -// Example -// -// Created by Александр Кравченков on 10.12.2017. -// Copyright © 2017 Кравченков Александр. All rights reserved. -// - -import Foundation - -public class UserMiniEntity: Codable { - public let id: Int - public let name: String - public let username: String - public let email: String -} diff --git a/Example/Example/Services/ListService/GetAnimalPagiongRequest.swift b/Example/Example/Services/ListService/GetAnimalPagiongRequest.swift deleted file mode 100644 index d69eda79..00000000 --- a/Example/Example/Services/ListService/GetAnimalPagiongRequest.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// GetAnimalPagiongRequest.swift -// Example -// -// Created by Alexander Kravchenkov on 06.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation -import CoreNetKit - -public class GetAnimalPagingRequest: BaseServerRequest<[AnimalEntity]>, ReusablePagingRequest { - - private struct Keys { - static let index = "index" - static let offset = "offset" - } - - private var startIndex = 0 - private var itemsOnPage = 0 - - public override init() { - self.itemsOnPage = 0 - self.startIndex = 0 - } - - public override func createAsyncServerRequest() -> CoreServerRequest { - let params = [Keys.index: self.startIndex, Keys.offset: self.itemsOnPage] - return BaseCoreServerRequest(method: .get, baseUrl: Urls.base, relativeUrl: Urls.Animals.list, headers: ["Content-Type": "application/json"], parameters: .simpleParams(params)) - } - - public func reuse(startIndex: Int, itemsOnPage: Int) { - self.startIndex = startIndex - self.itemsOnPage = itemsOnPage - } - - public override func handle(serverResponse: CoreServerResponse, completion: (ResponseResult<[AnimalEntity]>) -> Void) { - switch serverResponse.result { - case .failure(let error): - completion(.failure(error)) - case .success(let val, let cacheFlag): - guard let arr = val as? [[String: Any]] else { - completion(.failure(BaseServerError.badJsonFormat)) - return - } - - var result = [AnimalEntity]() - - arr.forEach { (dict) in - guard let animal = AnimalEntity(json: dict) else { - completion(.failure(BaseServerError.badJsonFormat)) - return - } - result.append(animal) - } - - completion(.success(result, cacheFlag)) - } - } -} diff --git a/Example/Example/Services/ListService/ListService.swift b/Example/Example/Services/ListService/ListService.swift deleted file mode 100644 index 855e83fd..00000000 --- a/Example/Example/Services/ListService/ListService.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// ListService.swift -// Example -// -// Created by Alexander Kravchenkov on 06.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation -import CoreNetKit - -class ListService { - - func getIterator(with start: Int, itemsOnPage: Int) -> IteratableContext<[AnimalEntity]> { - let request = GetAnimalPagingRequest() - let pagingContext = PagingRequestContext(request: request) - - return IteratableContext<[AnimalEntity]>(startIndex: start, itemsOnPage: itemsOnPage, context: pagingContext) - } -} diff --git a/Example/Example/Services/Urls.swift b/Example/Example/Services/Urls.swift deleted file mode 100644 index 12a438ab..00000000 --- a/Example/Example/Services/Urls.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// URLs.swift -// Example -// -// Created by Alexander Kravchenkov on 04.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation - -enum Urls { - static let base = "http://192.168.0.201:11117" - - enum Auth { - static let url = "/auth" - } - - enum Animals { - static let list = "/animals" - } -} diff --git a/Example/Example/Services/UserService/AuthRequest.swift b/Example/Example/Services/UserService/AuthRequest.swift deleted file mode 100644 index b6c5bcf7..00000000 --- a/Example/Example/Services/UserService/AuthRequest.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// AuthRequest.swift -// Example -// -// Created by Alexander Kravchenkov on 04.04.2018. -// Copyright © 2018 Кравченков Александр. All rights reserved. -// - -import Foundation -import CoreNetKit - -enum AuthErrors: LocalizedError { - case badCredentials(String) - - public var errorDescription: String? { - switch self { - case .badCredentials(let mesasge): - return mesasge - } - } - -} - -class AuthErrorMapper: ErrorMapperAdapter { - func map(json: [String : Any], httpCode: Int?) -> LocalizedError? { - guard httpCode == 403, let message = json["Message"] as? String else { - return nil - } - - return AuthErrors.badCredentials(message) - } -} - -class AuthRequest: BaseServerRequest { - - // MARK: - Nested - - private struct Keys { - static let email = "email" - static let password = "password" - - static let accessToken = "access-token" - static let refreshToken = "refresh-token" - } - - // MARK: - Private fields - - private let email: String - private let password: String - - // MARK: - Initializers - - init(email: String, password: String) { - self.email = email - self.password = password - } - - override func createAsyncServerRequest() -> CoreServerRequest { - let params = [Keys.email: self.email, Keys.password: self.password] - return BaseCoreServerRequest(method: .post, baseUrl: Urls.base, relativeUrl: Urls.Auth.url, - parameters: .simpleParams(params), errorMapper: AuthErrorMapper(), cacheAdapter: nil) - } - - override func handle(serverResponse: CoreServerResponse, completion: (ResponseResult) -> Void) { - switch serverResponse.result { - case .success(let value as [String: String], let flag) where value[Keys.accessToken] != nil && value[Keys.refreshToken] != nil: - let entity = AuthTokenEntity(accessToken: value[Keys.accessToken]!, refreshtoken: value[Keys.refreshToken]!) - completion(.success(entity, flag)) - case .failure(let error): - completion(.failure(error)) - default: - completion(.failure(BaseServerError.cantMapping)) - } - } -} diff --git a/Example/Example/Services/UserService/AuthService.swift b/Example/Example/Services/UserService/AuthService.swift deleted file mode 100644 index 18dbb7c7..00000000 --- a/Example/Example/Services/UserService/AuthService.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// LoginService.swift -// Example -// -// Created by Александр Кравченков on 09.12.2017. -// Copyright © 2017 Кравченков Александр. All rights reserved. -// - -import Foundation -import CoreNetKit - -class AuthService { - - func auth(by email: String, and passwod: String) -> ActionableContext { - let result = PassiveRequestContext() - self.getAuthToken(by: email, and: passwod) - .onCompleted { (entity) in - // TODO: write to store - result.performComplete(result: ()) - }.onError { (error) in - result.performError(error: error) - } - return result - } - - private func getAuthToken(by email: String, and passwod: String) -> ActionableContext { - let request = AuthRequest(email: email, password: passwod) - return ActiveRequestContext(request: request).perform() - } -} diff --git a/Example/Example/Utils/ErrorRepresentable.swift b/Example/Example/Utils/ErrorRepresentable.swift new file mode 100644 index 00000000..18e2cebc --- /dev/null +++ b/Example/Example/Utils/ErrorRepresentable.swift @@ -0,0 +1,28 @@ +// +// ErrorRepresentable.swift +// Example +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import SwiftMessages + +protocol ErrorRepresentable { + + @MainActor + func show(error: Error) +} + +extension ErrorRepresentable { + + @MainActor + func show(error: Error) { + let messageView = MessageView.viewFromNib(layout: .cardView) + messageView.configureTheme(.error) + messageView.configureDropShadow() + messageView.configureContent(title: "Error", body: error.localizedDescription, iconText: "😳") + messageView.button?.isHidden = true + SwiftMessages.show(view: messageView) + } +} diff --git a/Example/Modules/MockServer/MockServer/Auth/LoginResponseProvider.swift b/Example/Modules/MockServer/MockServer/Auth/LoginResponseProvider.swift new file mode 100644 index 00000000..cd4a4911 --- /dev/null +++ b/Example/Modules/MockServer/MockServer/Auth/LoginResponseProvider.swift @@ -0,0 +1,39 @@ +// +// LoginResponseProvider.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import Models + +enum LoginResponseProvider { + + // MARK: - Constants + + private enum Constants { + static let accessToken = "accessToken" + static let refreshToken = "refreshToken" + static let statusCode = 200 + } + + // MARK: - Methods + + static func provide() throws -> (HTTPURLResponse, Data) { + let model = AuthTokenResponseEntity( + accessToken: Constants.accessToken, + refreshToken: Constants.refreshToken + ) + let data = try JSONSerialization.data(withJSONObject: try model.toDTO().toRaw()) + return ( + HTTPURLResponse( + url: ServerConstants.hostURL, + statusCode: Constants.statusCode, + httpVersion: nil, + headerFields: nil + )!, + data + ) + } +} diff --git a/Example/Modules/MockServer/MockServer/Error/ErrorResponseProvider.swift b/Example/Modules/MockServer/MockServer/Error/ErrorResponseProvider.swift new file mode 100644 index 00000000..88ac4b2b --- /dev/null +++ b/Example/Modules/MockServer/MockServer/Error/ErrorResponseProvider.swift @@ -0,0 +1,23 @@ +// +// ErrorResponseProvider.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation + +enum ErrorResponseProvider { + + static func provide400Error() -> (HTTPURLResponse, Data) { + return ( + HTTPURLResponse( + url: ServerConstants.hostURL, + statusCode: 400, + httpVersion: nil, + headerFields: nil + )!, + Data() + ) + } +} diff --git a/Example/Modules/MockServer/MockServer/Group/GroupResponseProvider.swift b/Example/Modules/MockServer/MockServer/Group/GroupResponseProvider.swift new file mode 100644 index 00000000..9d294f97 --- /dev/null +++ b/Example/Modules/MockServer/MockServer/Group/GroupResponseProvider.swift @@ -0,0 +1,68 @@ +// +// GroupResponseProvider.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import Models + +enum GroupResponseProvider { + + // MARK: - Constants + + private enum Constants { + static let headerText = "Header" + static let headerImage = "https://loremflickr.com/500/500?random1" + static let bodyText = "Body" + static let bodyImage = "https://loremflickr.com/500/500?random2" + static let footerText = "Footer" + static let footerImage = "https://loremflickr.com/500/500?random3" + static let statusCode = 200 + } + + // MARK: - Methods + + static func provideHeader() throws -> (HTTPURLResponse, Data) { + let model = GroupHeaderResponseEntity(text: Constants.headerText, image: Constants.headerImage) + let data = try JSONSerialization.data(withJSONObject: try model.toDTO().toRaw()) + return ( + HTTPURLResponse( + url: ServerConstants.hostURL, + statusCode: Constants.statusCode, + httpVersion: nil, + headerFields: nil + )!, + data + ) + } + + static func provideBody() throws -> (HTTPURLResponse, Data) { + let model = GroupBodyResponseEntity(text: Constants.bodyText, image: Constants.bodyImage) + let data = try JSONSerialization.data(withJSONObject: try model.toDTO().toRaw()) + return ( + HTTPURLResponse( + url: ServerConstants.hostURL, + statusCode: Constants.statusCode, + httpVersion: nil, + headerFields: nil + )!, + data + ) + } + + static func provideFooter() throws -> (HTTPURLResponse, Data) { + let model = GroupFooterResponseEntity(text: Constants.footerText, image: Constants.footerImage) + let data = try JSONSerialization.data(withJSONObject: try model.toDTO().toRaw()) + return ( + HTTPURLResponse( + url: ServerConstants.hostURL, + statusCode: Constants.statusCode, + httpVersion: nil, + headerFields: nil + )!, + data + ) + } +} diff --git a/Example/Modules/MockServer/MockServer/MockServer.swift b/Example/Modules/MockServer/MockServer/MockServer.swift new file mode 100644 index 00000000..0e8de18d --- /dev/null +++ b/Example/Modules/MockServer/MockServer/MockServer.swift @@ -0,0 +1,43 @@ +// +// MockServer.swift +// Example +// +// Created by Andrei Frolov on 11.04.24. +// Copyright © 2024 Кравченков Александр. All rights reserved. +// + +import Foundation +import Models +import NodeKitMock + +public enum MockServer { + + public static func start() { + URLProtocolMock.stubbedRequestHandler = { request in + guard + let url = request.url, + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true), + urlComponents.host == ServerConstants.hostURL.absoluteString + else { + return ErrorResponseProvider.provide400Error() + } + + switch urlComponents.path { + case "/auth/login": + return try LoginResponseProvider.provide() + case "/pagination/list": + return try PaginationResponseProvider.provide(for: request) + case "/group/header": + return try GroupResponseProvider.provideHeader() + case "/group/body": + return try GroupResponseProvider.provideBody() + case "/group/footer": + return try GroupResponseProvider.provideFooter() + default: + break + } + + return ErrorResponseProvider.provide400Error() + } + } +} diff --git a/Example/Modules/MockServer/MockServer/Pagination/PaginationResponseProvider.swift b/Example/Modules/MockServer/MockServer/Pagination/PaginationResponseProvider.swift new file mode 100644 index 00000000..962d6731 --- /dev/null +++ b/Example/Modules/MockServer/MockServer/Pagination/PaginationResponseProvider.swift @@ -0,0 +1,94 @@ +// +// PaginationResponseProvider.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import Models + +enum PaginationResponseProvider { + + // MARK: - Constants + + private enum Constants { + static let indexKey = "index" + static let pageSizeKey = "pageSize" + static let title = "Pagination Item" + static let image = "https://loremflickr.com/200/300" + static let statusCode = 200 + static let itemsCount = 100 + } + + // MARK: - Private Parameters + + private static let items = (0...Constants.itemsCount).map { + let width = 300 + $0 + let height = 200 + $0 + return PaginationResponseEntity( + name: Constants.title + " \($0)", + image: Constants.image + "?random\($0 + 1)" + ) + } + + // MARK: - Methods + + static func provide(for request: URLRequest) throws -> (HTTPURLResponse, Data) { + guard + let index = getIndexParameter(from: request), + let pageSize = getPageSizeParameter(from: request) + else { + return ErrorResponseProvider.provide400Error() + } + + let responseArray = makeResponseArray(index: index, pageSize: pageSize) + let data = try JSONSerialization.data(withJSONObject: try responseArray.toDTO().toRaw()) + return ( + HTTPURLResponse( + url: ServerConstants.hostURL, + statusCode: Constants.statusCode, + httpVersion: nil, + headerFields: nil + )!, + data + ) + } +} + +// MARK: - Private Methods + +private extension PaginationResponseProvider { + + private static func getIndexParameter(from request: URLRequest) -> Int? { + guard + let url = request.url, + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true), + let index = urlComponents.queryItems?.first(where: { $0.name == Constants.indexKey })?.value + else { + return nil + } + + return Int(index) + } + + private static func getPageSizeParameter(from request: URLRequest) -> Int? { + guard + let url = request.url, + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true), + let pageSize = urlComponents.queryItems?.first(where: { $0.name == Constants.pageSizeKey })?.value + else { + return nil + } + + return Int(pageSize) + } + + private static func makeResponseArray(index: Int, pageSize: Int) -> [PaginationResponseEntity] { + guard index < Constants.itemsCount else { + return [] + } + + return Array(items.suffix(from: index).prefix(pageSize)) + } +} diff --git a/Example/Modules/MockServer/MockServer/ServerConstants.swift b/Example/Modules/MockServer/MockServer/ServerConstants.swift new file mode 100644 index 00000000..9d746777 --- /dev/null +++ b/Example/Modules/MockServer/MockServer/ServerConstants.swift @@ -0,0 +1,12 @@ +// +// ServerConstants.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation + +enum ServerConstants { + static let hostURL = URL(string: "www.mockurl.com")! +} diff --git a/Example/Modules/MockServer/Package.swift b/Example/Modules/MockServer/Package.swift new file mode 100644 index 00000000..11fb8722 --- /dev/null +++ b/Example/Modules/MockServer/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.7 +import PackageDescription + +let package = Package( + name: "MockServer", + platforms: [ + .macOS(.v11), + .iOS(.v13), + ], + products: [ + .library( + name: "MockServer", + targets: ["MockServer"] + ) + ], + dependencies: [ + .package(path: "../../../NodeKit"), + .package(path: "../Models") + ], + targets: [ + .target( + name: "MockServer", + dependencies: [ + "NodeKit", + "Models" + ], + path: "MockServer" + ) + ] +) diff --git a/Example/Modules/Models/Models/Requests/Auth/AuthRequestEntity.swift b/Example/Modules/Models/Models/Requests/Auth/AuthRequestEntity.swift new file mode 100644 index 00000000..9567da9f --- /dev/null +++ b/Example/Modules/Models/Models/Requests/Auth/AuthRequestEntity.swift @@ -0,0 +1,30 @@ +// +// AuthRequestEntity.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct AuthRequestEntity { + + // MARK: - Properties + + public let email: String + public let password: String + + // MARK: - Initialization + + public init(email: String, password: String) { + self.email = email + self.password = password + } +} + +extension AuthRequestEntity: DTOEncodable { + public typealias DTO = AuthRequestEntry + + public func toDTO() throws -> AuthRequestEntry { + return AuthRequestEntry(email: email, password: password) + } +} diff --git a/Example/Modules/Models/Models/Requests/Auth/AuthRequestEntry.swift b/Example/Modules/Models/Models/Requests/Auth/AuthRequestEntry.swift new file mode 100644 index 00000000..f8f9b315 --- /dev/null +++ b/Example/Modules/Models/Models/Requests/Auth/AuthRequestEntry.swift @@ -0,0 +1,16 @@ +// +// AuthRequestEntry.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct AuthRequestEntry: Codable { + let email: String + let password: String +} + +extension AuthRequestEntry: RawEncodable { + public typealias Raw = Json +} diff --git a/Example/Modules/Models/Models/Requests/Pagination/PaginationRequestEntity.swift b/Example/Modules/Models/Models/Requests/Pagination/PaginationRequestEntity.swift new file mode 100644 index 00000000..f3a2577e --- /dev/null +++ b/Example/Modules/Models/Models/Requests/Pagination/PaginationRequestEntity.swift @@ -0,0 +1,30 @@ +// +// PaginationRequestEntity.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct PaginationRequestEntity { + + // MARK: - Properties + + public let index: Int + public let pageSize: Int + + // MARK: - Initialization + + public init(index: Int, pageSize: Int) { + self.index = index + self.pageSize = pageSize + } +} + +extension PaginationRequestEntity: DTOEncodable { + public typealias DTO = PaginationRequestEntry + + public func toDTO() throws -> PaginationRequestEntry { + PaginationRequestEntry(index: index, pageSize: pageSize) + } +} diff --git a/Example/Modules/Models/Models/Requests/Pagination/PaginationRequestEntry.swift b/Example/Modules/Models/Models/Requests/Pagination/PaginationRequestEntry.swift new file mode 100644 index 00000000..4a2af2fa --- /dev/null +++ b/Example/Modules/Models/Models/Requests/Pagination/PaginationRequestEntry.swift @@ -0,0 +1,16 @@ +// +// PaginationRequestEntry.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct PaginationRequestEntry { + let index: Int + let pageSize: Int +} + +extension PaginationRequestEntry: Codable, RawEncodable { + public typealias Raw = Json +} diff --git a/Example/Modules/Models/Models/Responses/Auth/AuthTokenResponseEntity.swift b/Example/Modules/Models/Models/Responses/Auth/AuthTokenResponseEntity.swift new file mode 100644 index 00000000..8d1ff424 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Auth/AuthTokenResponseEntity.swift @@ -0,0 +1,35 @@ +// +// AuthTokenResponseEntity.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import Foundation +import NodeKit + +public struct AuthTokenResponseEntity { + + // MARK: - Properties + + public let accessToken: String + public let refreshToken: String + + // MARK: - Initialization + + public init(accessToken: String, refreshToken: String) { + self.accessToken = accessToken + self.refreshToken = refreshToken + } +} + +extension AuthTokenResponseEntity: DTOConvertible { + public typealias DTO = AuthTokenResponseEntry + + public func toDTO() throws -> AuthTokenResponseEntry { + return AuthTokenResponseEntry(accessToken: accessToken, refreshToken: refreshToken) + } + + public static func from(dto: AuthTokenResponseEntry) throws -> AuthTokenResponseEntity { + return AuthTokenResponseEntity(accessToken: dto.accessToken, refreshToken: dto.refreshToken) + } +} diff --git a/Example/Modules/Models/Models/Responses/Auth/AuthTokenResponseEntry.swift b/Example/Modules/Models/Models/Responses/Auth/AuthTokenResponseEntry.swift new file mode 100644 index 00000000..c9f2e8cb --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Auth/AuthTokenResponseEntry.swift @@ -0,0 +1,16 @@ +// +// AuthTokenResponseEntry.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct AuthTokenResponseEntry { + let accessToken: String + let refreshToken: String +} + +extension AuthTokenResponseEntry: Codable, RawMappable { + public typealias Raw = Json +} diff --git a/Example/Modules/Models/Models/Responses/Group/GroupBodyResponseEntity.swift b/Example/Modules/Models/Models/Responses/Group/GroupBodyResponseEntity.swift new file mode 100644 index 00000000..681da681 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Group/GroupBodyResponseEntity.swift @@ -0,0 +1,35 @@ +// +// GroupBodyResponseEntity.swift +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import NodeKit + +public struct GroupBodyResponseEntity { + + // MARK: - Properties + + public let text: String + public let image: String + + // MARK: - Initialization + + public init(text: String, image: String) { + self.text = text + self.image = image + } +} + +extension GroupBodyResponseEntity: DTOConvertible { + public typealias DTO = GroupBodyResponseEntry + + public func toDTO() throws -> GroupBodyResponseEntry { + return GroupBodyResponseEntry(text: text, image: image) + } + + public static func from(dto: GroupBodyResponseEntry) throws -> GroupBodyResponseEntity { + return GroupBodyResponseEntity(text: dto.text, image: dto.image) + } +} diff --git a/Example/Modules/Models/Models/Responses/Group/GroupBodyResponseEntry.swift b/Example/Modules/Models/Models/Responses/Group/GroupBodyResponseEntry.swift new file mode 100644 index 00000000..eaba9388 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Group/GroupBodyResponseEntry.swift @@ -0,0 +1,16 @@ +// +// GroupBodyResponseEntry.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct GroupBodyResponseEntry { + let text: String + let image: String +} + +extension GroupBodyResponseEntry: Codable, RawMappable { + public typealias Raw = Json +} diff --git a/Example/Modules/Models/Models/Responses/Group/GroupFooterResponseEntity.swift b/Example/Modules/Models/Models/Responses/Group/GroupFooterResponseEntity.swift new file mode 100644 index 00000000..a8850668 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Group/GroupFooterResponseEntity.swift @@ -0,0 +1,35 @@ +// +// GroupFooterResponseEntity.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import Foundation +import NodeKit + +public struct GroupFooterResponseEntity { + + // MARK: - Properties + + public let text: String + public let image: String + + // MARK: - Initialization + + public init(text: String, image: String) { + self.text = text + self.image = image + } +} + +extension GroupFooterResponseEntity: DTOConvertible { + public typealias DTO = GroupFooterResponseEntry + + public func toDTO() throws -> GroupFooterResponseEntry { + return GroupFooterResponseEntry(text: text, image: image) + } + + public static func from(dto: GroupFooterResponseEntry) throws -> GroupFooterResponseEntity { + return GroupFooterResponseEntity(text: dto.text, image: dto.image) + } +} diff --git a/Example/Modules/Models/Models/Responses/Group/GroupFooterResponseEntry.swift b/Example/Modules/Models/Models/Responses/Group/GroupFooterResponseEntry.swift new file mode 100644 index 00000000..532957c6 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Group/GroupFooterResponseEntry.swift @@ -0,0 +1,16 @@ +// +// GroupFooterResponseEntry.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct GroupFooterResponseEntry { + let text: String + let image: String +} + +extension GroupFooterResponseEntry: Codable, RawMappable { + public typealias Raw = Json +} diff --git a/Example/Modules/Models/Models/Responses/Group/GroupHeaderResponseEntity.swift b/Example/Modules/Models/Models/Responses/Group/GroupHeaderResponseEntity.swift new file mode 100644 index 00000000..53287513 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Group/GroupHeaderResponseEntity.swift @@ -0,0 +1,35 @@ +// +// GroupHeaderResponseEntity.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import Foundation +import NodeKit + +public struct GroupHeaderResponseEntity { + + // MARK: - Properties + + public let text: String + public let image: String + + // MARK: - Initialization + + public init(text: String, image: String) { + self.text = text + self.image = image + } +} + +extension GroupHeaderResponseEntity: DTOConvertible { + public typealias DTO = GroupHeaderResponseEntry + + public func toDTO() throws -> GroupHeaderResponseEntry { + return GroupHeaderResponseEntry(text: text, image: image) + } + + public static func from(dto: GroupHeaderResponseEntry) throws -> GroupHeaderResponseEntity { + return GroupHeaderResponseEntity(text: dto.text, image: dto.image) + } +} diff --git a/Example/Modules/Models/Models/Responses/Group/GroupHeaderResponseEntry.swift b/Example/Modules/Models/Models/Responses/Group/GroupHeaderResponseEntry.swift new file mode 100644 index 00000000..33ee0c8f --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Group/GroupHeaderResponseEntry.swift @@ -0,0 +1,16 @@ +// +// GroupHeaderResponseEntry.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct GroupHeaderResponseEntry { + let text: String + let image: String +} + +extension GroupHeaderResponseEntry: Codable, RawMappable { + public typealias Raw = Json +} diff --git a/Example/Modules/Models/Models/Responses/Pagination/PaginationResponseEntity.swift b/Example/Modules/Models/Models/Responses/Pagination/PaginationResponseEntity.swift new file mode 100644 index 00000000..5ccfd7f4 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Pagination/PaginationResponseEntity.swift @@ -0,0 +1,34 @@ +// +// PaginationResponseEntity.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct PaginationResponseEntity { + + // MARK: - Properties + + public let name: String + public let image: String + + // MARK: - Initialization + + public init(name: String, image: String) { + self.name = name + self.image = image + } +} + +extension PaginationResponseEntity: DTOConvertible { + public typealias DTO = PaginationResponseEntry + + public static func from(dto: PaginationResponseEntry) throws -> PaginationResponseEntity { + return PaginationResponseEntity(name: dto.name, image: dto.image) + } + + public func toDTO() throws -> PaginationResponseEntry { + return PaginationResponseEntry(name: name, image: image) + } +} diff --git a/Example/Modules/Models/Models/Responses/Pagination/PaginationResponseEntry.swift b/Example/Modules/Models/Models/Responses/Pagination/PaginationResponseEntry.swift new file mode 100644 index 00000000..110748a7 --- /dev/null +++ b/Example/Modules/Models/Models/Responses/Pagination/PaginationResponseEntry.swift @@ -0,0 +1,16 @@ +// +// PaginationResponseEntry.swift +// +// Created by Andrei Frolov on 11.04.24. +// + +import NodeKit + +public struct PaginationResponseEntry { + let name: String + let image: String +} + +extension PaginationResponseEntry: Codable, RawMappable { + public typealias Raw = Json +} diff --git a/Example/Modules/Models/Package.swift b/Example/Modules/Models/Package.swift new file mode 100644 index 00000000..8c397720 --- /dev/null +++ b/Example/Modules/Models/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:5.7 +import PackageDescription + +let package = Package( + name: "Models", + platforms: [ + .macOS(.v11), + .iOS(.v13), + ], + products: [ + .library( + name: "Models", + targets: ["Models"] + ) + ], + dependencies: [ + .package(path: "../../../NodeKit") + ], + targets: [ + .target( + name: "Models", + dependencies: [ + "NodeKit" + ], + path: "Models" + ) + ] +) diff --git a/Example/Modules/Services/Package.swift b/Example/Modules/Services/Package.swift new file mode 100644 index 00000000..bf5f91be --- /dev/null +++ b/Example/Modules/Services/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.7 +import PackageDescription + +let package = Package( + name: "Services", + platforms: [ + .macOS(.v11), + .iOS(.v13), + ], + products: [ + .library( + name: "Services", + targets: ["Services"] + ) + ], + dependencies: [ + .package(path: "../Models"), + .package(path: "../../../NodeKit") + ], + targets: [ + .target( + name: "Services", + dependencies: [ + "Models", + "NodeKit" + ], + path: "Services" + ) + ] +) diff --git a/Example/Modules/Services/Services/AuthService/AuthService.swift b/Example/Modules/Services/Services/AuthService/AuthService.swift new file mode 100644 index 00000000..bb0aab57 --- /dev/null +++ b/Example/Modules/Services/Services/AuthService/AuthService.swift @@ -0,0 +1,28 @@ +// +// LoginService.swift +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import Models +import NodeKit +import NodeKitMock + +public protocol AuthServiceProtocol { + func auth(by email: String, and passwod: String) async -> NodeResult +} + +public struct AuthService: AuthServiceProtocol { + + public init() { } + + public func auth(by email: String, and passwod: String) async -> NodeResult { + return await UrlChainsBuilder() + .set(session: NetworkMock().urlSession) + .encode(as: .urlQuery) + .route(.post, .login) + .build() + .process(AuthRequestEntity(email: email, password: passwod)) + } +} diff --git a/Example/Modules/Services/Services/AuthService/AuthURLProvider.swift b/Example/Modules/Services/Services/AuthService/AuthURLProvider.swift new file mode 100644 index 00000000..bfd63dc6 --- /dev/null +++ b/Example/Modules/Services/Services/AuthService/AuthURLProvider.swift @@ -0,0 +1,25 @@ +// +// AuthURLProvider.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import NodeKit + +enum AuthURLProvider: UrlRouteProvider { + private static var base: UrlRouteProvider = NavigationURLProvider.auth + + case login + case logout + + func url() throws -> URL { + switch self { + case .login: + return try Self.base.url() + "/login" + case .logout: + return try Self.base.url() + "/logout" + } + } +} diff --git a/Example/Modules/Services/Services/GroupService/GroupService.swift b/Example/Modules/Services/Services/GroupService/GroupService.swift new file mode 100644 index 00000000..12762c15 --- /dev/null +++ b/Example/Modules/Services/Services/GroupService/GroupService.swift @@ -0,0 +1,44 @@ +// +// GroupService.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import NodeKit +import NodeKitMock +import Models + +public protocol GroupServiceProtocol { + func header() async -> NodeResult + func body() async -> NodeResult + func footer() async -> NodeResult +} + +public final class GroupService: GroupServiceProtocol { + + public init() { } + + public func header() async -> NodeResult { + return await result(from: .header) + } + + public func body() async -> NodeResult { + return await result(from: .body) + } + + public func footer() async -> NodeResult { + return await result(from: .footer) + } + + private func result( + from route: GroupURLProvider + ) async -> NodeResult where T.DTO.Raw == Json { + return await UrlChainsBuilder() + .set(session: NetworkMock().urlSession) + .route(.get, route) + .build() + .process() + } +} diff --git a/Example/Modules/Services/Services/GroupService/GroupURLProvider.swift b/Example/Modules/Services/Services/GroupService/GroupURLProvider.swift new file mode 100644 index 00000000..22d39384 --- /dev/null +++ b/Example/Modules/Services/Services/GroupService/GroupURLProvider.swift @@ -0,0 +1,28 @@ +// +// GroupURLProvider.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import NodeKit + +enum GroupURLProvider: UrlRouteProvider { + private static var base: UrlRouteProvider = NavigationURLProvider.group + + case header + case body + case footer + + func url() throws -> URL { + switch self { + case .header: + return try Self.base.url() + "/header" + case .body: + return try Self.base.url() + "/body" + case .footer: + return try Self.base.url() + "/footer" + } + } +} diff --git a/Example/Modules/Services/Services/PaginationService/PaginationContentDataProvider.swift b/Example/Modules/Services/Services/PaginationService/PaginationContentDataProvider.swift new file mode 100644 index 00000000..33061422 --- /dev/null +++ b/Example/Modules/Services/Services/PaginationService/PaginationContentDataProvider.swift @@ -0,0 +1,25 @@ +// +// PaginationContentDataProvider.swift +// +// Created by Andrei Frolov on 15.04.24. +// + +import Models +import NodeKit +import NodeKitMock + +public struct PaginationContentDataProvider: AsyncPagerDataProvider { + public typealias Value = [PaginationResponseEntity] + + public init() { } + + public func provide(for index: Int, with pageSize: Int) async -> NodeResult> { + return await UrlChainsBuilder() + .set(session: NetworkMock().urlSession) + .encode(as: .urlQuery) + .route(.get, .list) + .build() + .process(PaginationRequestEntity(index: index, pageSize: pageSize)) + .map { AsyncPagerData(value: $0, len: $0.count) } + } +} diff --git a/Example/Modules/Services/Services/PaginationService/PaginationURLProvider.swift b/Example/Modules/Services/Services/PaginationService/PaginationURLProvider.swift new file mode 100644 index 00000000..56e0307a --- /dev/null +++ b/Example/Modules/Services/Services/PaginationService/PaginationURLProvider.swift @@ -0,0 +1,22 @@ +// +// PaginationURLProvider.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import NodeKit + +enum PaginationURLProvider: UrlRouteProvider { + private static var base: UrlRouteProvider = NavigationURLProvider.pagination + + case list + + func url() throws -> URL { + switch self { + case .list: + return try Self.base.url() + "/list" + } + } +} diff --git a/Example/Modules/Services/Services/URLProviders.swift b/Example/Modules/Services/Services/URLProviders.swift new file mode 100644 index 00000000..ed6ddb4c --- /dev/null +++ b/Example/Modules/Services/Services/URLProviders.swift @@ -0,0 +1,35 @@ +// +// URLProviders.swift +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation +import NodeKit + +enum RootURLProvider: UrlRouteProvider { + case root + + func url() throws -> URL { + return URL(string: "http://www.mockurl.com")! + } +} + +enum NavigationURLProvider: UrlRouteProvider { + private static var base: UrlRouteProvider = RootURLProvider.root + + case auth + case pagination + case group + + func url() throws -> URL { + switch self { + case .auth: + return try Self.base.url() + "/auth" + case .pagination: + return try Self.base.url() + "/pagination" + case .group: + return try Self.base.url() + "/group" + } + } +} diff --git a/Example/Podfile b/Example/Podfile deleted file mode 100644 index 98482c45..00000000 --- a/Example/Podfile +++ /dev/null @@ -1,15 +0,0 @@ -# Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' - -target 'Example' do - # Comment the next line if you're not using Swift and don't want to use dynamic frameworks - use_frameworks! - - # Pods for Example - - pod 'CoreNetKit', :path => '../' - pod 'AlamofireImage', '~> 3.3' - pod 'SwiftMessages' - pod 'ReactiveDataDisplayManager', :git=>"https://github.com/LastSprint/ReactiveDataDisplayManager", :commit=>"8c8c9d07f38c3dbe8f5cb56a300521e1f941f5e8" - -end diff --git a/Example/Podfile.lock b/Example/Podfile.lock deleted file mode 100644 index 64b68eb5..00000000 --- a/Example/Podfile.lock +++ /dev/null @@ -1,39 +0,0 @@ -PODS: - - Alamofire (4.7.1) - - AlamofireImage (3.3.1): - - Alamofire (~> 4.5) - - CoreNetKit (1.0.2): - - Alamofire - - ReactiveDataDisplayManager (2.1.2): - - ReactiveDataDisplayManager/Core (= 2.1.2) - - ReactiveDataDisplayManager/Core (2.1.2) - - SwiftMessages (4.1.0) - -DEPENDENCIES: - - AlamofireImage (~> 3.3) - - CoreNetKit (from `../`) - - ReactiveDataDisplayManager (from `https://github.com/LastSprint/ReactiveDataDisplayManager`, commit `8c8c9d07f38c3dbe8f5cb56a300521e1f941f5e8`) - - SwiftMessages - -EXTERNAL SOURCES: - CoreNetKit: - :path: ../ - ReactiveDataDisplayManager: - :commit: 8c8c9d07f38c3dbe8f5cb56a300521e1f941f5e8 - :git: https://github.com/LastSprint/ReactiveDataDisplayManager - -CHECKOUT OPTIONS: - ReactiveDataDisplayManager: - :commit: 8c8c9d07f38c3dbe8f5cb56a300521e1f941f5e8 - :git: https://github.com/LastSprint/ReactiveDataDisplayManager - -SPEC CHECKSUMS: - Alamofire: 68d7d521118d49c615a8d2214d87cdf525599d30 - AlamofireImage: 3b35b586853abaf94ca1250f4e94cff3c21a4c0d - CoreNetKit: 07c04729a5bfd05fbd056b80644814c9f82b5544 - ReactiveDataDisplayManager: 4d153410824b9d8a7d45389fae218943a3fd3f50 - SwiftMessages: 1bacc783c8f10bdfdc57c14860cae8a1f1fa6591 - -PODFILE CHECKSUM: d14f9e929fcb1cf659b839d7789da9e8aabf7392 - -COCOAPODS: 1.3.1 diff --git a/LoadPdfPlayground.playground/Contents.swift b/LoadPdfPlayground.playground/Contents.swift deleted file mode 100644 index 4e5a8b40..00000000 --- a/LoadPdfPlayground.playground/Contents.swift +++ /dev/null @@ -1,67 +0,0 @@ -import NodeKit -import PlaygroundSupport -import PDFKit - -enum CustomError: Error { - case badUrl -} - -enum Endpoint { - case loadPDF -} - -extension URL { - static func from(_ string: String) throws -> URL { - guard let url = URL(string: string) else { - throw CustomError.badUrl - } - return url - } -} - -extension Endpoint: UrlRouteProvider { - func url() throws -> URL { - switch self { - case .loadPDF: - return try .from("https://lastsprint.dev/t.pdf") - } - } -} - -class ViewController : UIViewController { - - var pdfView: UIView! - - override func viewDidLoad() { - super.viewDidLoad() - - self.view.addSubview(pdfView) - pdfView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - pdfView.topAnchor.constraint(equalTo: view.topAnchor), - pdfView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - pdfView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - pdfView.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - - } -} - -func loadPdf() -> Observer { - return UrlChainsBuilder() - .route(.get, Endpoint.loadPDF) - .loadData() - .process() -} - -loadPdf() - .dispatchOn(.main) - .onCompleted { data in - let view = PDFView() - let pdf = PDFDocument(data: data) - view.document = pdf - let vc = ViewController() - vc.pdfView = view - PlaygroundPage.current.liveView = vc - } - diff --git a/LoadPdfPlayground.playground/contents.xcplayground b/LoadPdfPlayground.playground/contents.xcplayground deleted file mode 100644 index 5da2641c..00000000 --- a/LoadPdfPlayground.playground/contents.xcplayground +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Makefile b/Makefile index 878e7915..44c1a5fa 100644 --- a/Makefile +++ b/Makefile @@ -8,23 +8,23 @@ init: ## Used to build target. Usually, it is not called manually, it is necessary for the CI to work. build: - xcodebuild clean build -scheme NodeKit -sdk iphonesimulator | bundle exec xcpretty -c + xcodebuild clean build -project ./NodeKit/NodeKit.xcodeproj -scheme NodeKit -sdk iphonesimulator | bundle exec xcpretty -c ## Used to build target with SPM dependencies. Usually, it is not called manually, it is necessary for the CI to work. spm_build: - swift package clean - swift build --sdk "`xcrun -sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios17.4-simulator" -Xswiftc "-lswiftUIKit" + cd ./NodeKit && swift package clean + cd ./NodeKit && swift build --sdk "`xcrun -sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios17.4-simulator" -Xswiftc "-lswiftUIKit" ## Run tests and create coverage report test: rm -rf DerivedData - mkdir CoverageReports - xcodebuild test -scheme NodeKit -derivedDataPath DerivedData -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -enableCodeCoverage YES -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.4' | bundle exec xcpretty -c + mkdir -p CoverageReports + xcodebuild test -project ./NodeKit/NodeKit.xcodeproj -scheme NodeKit -derivedDataPath DerivedData -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -enableCodeCoverage YES -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.4' | bundle exec xcpretty -c ./xcresultparser/xcresultparser --output-format cobertura DerivedData/Logs/Test/*.xcresult > ./CoverageReports/coverage.xml ## Created documentation by comments from code doc: - bundle exec jazzy --clean --build-tool-arguments -scheme,NodeKit,-sdk,iphonesimulator --output "docs" + bundle exec jazzy --clean --build-tool-arguments -project,./NodeKit/NodeKit.xcodeproj,-scheme,NodeKit,-sdk,iphonesimulator --output "docs" # COLORS GREEN := $(shell tput -Txterm setaf 2) diff --git a/NodeKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/NodeKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/NodeKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/NodeKit.xcodeproj/project.pbxproj b/NodeKit/NodeKit.xcodeproj/project.pbxproj similarity index 97% rename from NodeKit.xcodeproj/project.pbxproj rename to NodeKit/NodeKit.xcodeproj/project.pbxproj index 00a10f88..6936db76 100644 --- a/NodeKit.xcodeproj/project.pbxproj +++ b/NodeKit/NodeKit.xcodeproj/project.pbxproj @@ -100,7 +100,10 @@ 50816B212BC707BF00A43F3D /* MockError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50816A4E2BC6F83D00A43F3D /* MockError.swift */; }; 50816B222BC707C100A43F3D /* MultipartFormDataFactoryMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50816A502BC6F83D00A43F3D /* MultipartFormDataFactoryMock.swift */; }; 50816B232BC707C300A43F3D /* MultipartFormDataMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50816A472BC6F83C00A43F3D /* MultipartFormDataMock.swift */; }; - 50816B3D2BC7123A00A43F3D /* NodeKitThirdParty in Frameworks */ = {isa = PBXBuildFile; productRef = 50816B3C2BC7123A00A43F3D /* NodeKitThirdParty */; settings = {ATTRIBUTES = (Required, ); }; }; + 5097DF3C2BCD556E00D422EE /* AsyncPagerDataProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF3B2BCD556E00D422EE /* AsyncPagerDataProviderMock.swift */; }; + 5097DF462BCD628400D422EE /* AsyncPagerDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DF452BCD628300D422EE /* AsyncPagerDataProvider.swift */; }; + 5097DFAE2BCDFA8300D422EE /* NodeKitThirdParty in Frameworks */ = {isa = PBXBuildFile; productRef = 5097DFAD2BCDFA8300D422EE /* NodeKitThirdParty */; }; + 5097DFB02BCDFB5200D422EE /* CancellableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5097DFAF2BCDFB5200D422EE /* CancellableTask.swift */; }; 50B6838F2BBF3615001F7EA3 /* AccessSafeNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B6838E2BBF3615001F7EA3 /* AccessSafeNodeTests.swift */; }; 50C8EB282BBD7A2200C5CB93 /* AsyncStreamCombineNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB272BBD7A2200C5CB93 /* AsyncStreamCombineNode.swift */; }; 50C8EB2A2BBD7DEA00C5CB93 /* CombineNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB292BBD7DEA00C5CB93 /* CombineNode.swift */; }; @@ -110,7 +113,7 @@ 50C8EB362BBD9CBF00C5CB93 /* NodeResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB352BBD9CBF00C5CB93 /* NodeResultTests.swift */; }; 50C8EB382BBDBA3B00C5CB93 /* AsyncCombineNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB372BBDBA3B00C5CB93 /* AsyncCombineNode.swift */; }; 50C8EB3A2BBDBBD300C5CB93 /* AsyncCombineNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB392BBDBBD300C5CB93 /* AsyncCombineNodeTests.swift */; }; - 50C8EB3C2BBEB5E300C5CB93 /* OffsetAsyncPagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB3B2BBEB5E300C5CB93 /* OffsetAsyncPagerTests.swift */; }; + 50C8EB3C2BBEB5E300C5CB93 /* AsyncPagerIteratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB3B2BBEB5E300C5CB93 /* AsyncPagerIteratorTests.swift */; }; 50C8EB472BBF14CC00C5CB93 /* TechnicaErrorMapperNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB462BBF14CC00C5CB93 /* TechnicaErrorMapperNodeTests.swift */; }; 50C8EB492BBF185300C5CB93 /* RequestCreatorNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB482BBF185300C5CB93 /* RequestCreatorNodeTests.swift */; }; 50C8EB4E2BBF1E9700C5CB93 /* HeaderInjectorNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C8EB4D2BBF1E9700C5CB93 /* HeaderInjectorNodeTests.swift */; }; @@ -185,7 +188,7 @@ 90B609B5283E1287006F4309 /* ParametersEncoding+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60971283E1287006F4309 /* ParametersEncoding+Alamofire.swift */; }; 90B609B6283E1287006F4309 /* CodableRawMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60974283E1287006F4309 /* CodableRawMapper.swift */; }; 90B609B7283E1287006F4309 /* MetadataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60975283E1287006F4309 /* MetadataProvider.swift */; }; - 90B609B9283E1287006F4309 /* OffsetAsyncPager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60978283E1287006F4309 /* OffsetAsyncPager.swift */; }; + 90B609B9283E1287006F4309 /* AsyncPagerIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60978283E1287006F4309 /* AsyncPagerIterator.swift */; }; 90B609BA283E1287006F4309 /* AsyncIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B60979283E1287006F4309 /* AsyncIterator.swift */; }; 90B609BB283E1287006F4309 /* StateStorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B6097A283E1287006F4309 /* StateStorable.swift */; }; 90B609BD283E1287006F4309 /* UrlRouting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90B6097D283E1287006F4309 /* UrlRouting.swift */; }; @@ -324,6 +327,9 @@ 50816A6E2BC6F85C00A43F3D /* RawDecodableMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawDecodableMock.swift; sourceTree = ""; }; 50816A6F2BC6F85C00A43F3D /* URLSessionDataTaskActorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionDataTaskActorMock.swift; sourceTree = ""; }; 50816AE42BC706D100A43F3D /* NodeKitMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NodeKitMock.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5097DF3B2BCD556E00D422EE /* AsyncPagerDataProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncPagerDataProviderMock.swift; sourceTree = ""; }; + 5097DF452BCD628300D422EE /* AsyncPagerDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncPagerDataProvider.swift; sourceTree = ""; }; + 5097DFAF2BCDFB5200D422EE /* CancellableTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancellableTask.swift; sourceTree = ""; }; 50B6838E2BBF3615001F7EA3 /* AccessSafeNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessSafeNodeTests.swift; sourceTree = ""; }; 50B683912BBF3816001F7EA3 /* TransportUrlRequest+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransportUrlRequest+Equatable.swift"; sourceTree = ""; }; 50C8EB272BBD7A2200C5CB93 /* AsyncStreamCombineNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncStreamCombineNode.swift; sourceTree = ""; }; @@ -334,7 +340,7 @@ 50C8EB352BBD9CBF00C5CB93 /* NodeResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeResultTests.swift; sourceTree = ""; }; 50C8EB372BBDBA3B00C5CB93 /* AsyncCombineNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncCombineNode.swift; sourceTree = ""; }; 50C8EB392BBDBBD300C5CB93 /* AsyncCombineNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncCombineNodeTests.swift; sourceTree = ""; }; - 50C8EB3B2BBEB5E300C5CB93 /* OffsetAsyncPagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OffsetAsyncPagerTests.swift; sourceTree = ""; }; + 50C8EB3B2BBEB5E300C5CB93 /* AsyncPagerIteratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncPagerIteratorTests.swift; sourceTree = ""; }; 50C8EB3D2BBEC1E700C5CB93 /* DispatchQueue+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Extension.swift"; sourceTree = ""; }; 50C8EB462BBF14CC00C5CB93 /* TechnicaErrorMapperNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TechnicaErrorMapperNodeTests.swift; sourceTree = ""; }; 50C8EB482BBF185300C5CB93 /* RequestCreatorNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestCreatorNodeTests.swift; sourceTree = ""; }; @@ -412,7 +418,7 @@ 90B60971283E1287006F4309 /* ParametersEncoding+Alamofire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParametersEncoding+Alamofire.swift"; sourceTree = ""; }; 90B60974283E1287006F4309 /* CodableRawMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableRawMapper.swift; sourceTree = ""; }; 90B60975283E1287006F4309 /* MetadataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetadataProvider.swift; sourceTree = ""; }; - 90B60978283E1287006F4309 /* OffsetAsyncPager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OffsetAsyncPager.swift; sourceTree = ""; }; + 90B60978283E1287006F4309 /* AsyncPagerIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncPagerIterator.swift; sourceTree = ""; }; 90B60979283E1287006F4309 /* AsyncIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncIterator.swift; sourceTree = ""; }; 90B6097A283E1287006F4309 /* StateStorable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateStorable.swift; sourceTree = ""; }; 90B6097D283E1287006F4309 /* UrlRouting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UrlRouting.swift; sourceTree = ""; }; @@ -460,7 +466,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 50816B3D2BC7123A00A43F3D /* NodeKitThirdParty in Frameworks */, + 5097DFAE2BCDFA8300D422EE /* NodeKitThirdParty in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -639,6 +645,7 @@ 50816A4E2BC6F83D00A43F3D /* MockError.swift */, 50816A502BC6F83D00A43F3D /* MultipartFormDataFactoryMock.swift */, 50816A472BC6F83C00A43F3D /* MultipartFormDataMock.swift */, + 5097DF3B2BCD556E00D422EE /* AsyncPagerDataProviderMock.swift */, ); path = NodeKitMock; sourceTree = ""; @@ -1008,6 +1015,7 @@ 90B60972283E1287006F4309 /* Utils */ = { isa = PBXGroup; children = ( + 5097DFAF2BCDFB5200D422EE /* CancellableTask.swift */, 90B60973283E1287006F4309 /* Mapping */, 90B60975283E1287006F4309 /* MetadataProvider.swift */, 90B60976283E1287006F4309 /* AsyncIterator */, @@ -1028,7 +1036,8 @@ 90B60976283E1287006F4309 /* AsyncIterator */ = { isa = PBXGroup; children = ( - 90B60978283E1287006F4309 /* OffsetAsyncPager.swift */, + 5097DF452BCD628300D422EE /* AsyncPagerDataProvider.swift */, + 90B60978283E1287006F4309 /* AsyncPagerIterator.swift */, 90B60979283E1287006F4309 /* AsyncIterator.swift */, 90B6097A283E1287006F4309 /* StateStorable.swift */, ); @@ -1083,7 +1092,7 @@ 90B609C6283E16DC006F4309 /* AsyncIterator */ = { isa = PBXGroup; children = ( - 50C8EB3B2BBEB5E300C5CB93 /* OffsetAsyncPagerTests.swift */, + 50C8EB3B2BBEB5E300C5CB93 /* AsyncPagerIteratorTests.swift */, ); path = AsyncIterator; sourceTree = ""; @@ -1240,7 +1249,7 @@ ); name = NodeKit; packageProductDependencies = ( - 50816B3C2BC7123A00A43F3D /* NodeKitThirdParty */, + 5097DFAD2BCDFA8300D422EE /* NodeKitThirdParty */, ); productName = NodeKit; productReference = 90B608CB283E1110006F4309 /* NodeKit.framework */; @@ -1298,7 +1307,7 @@ ); mainGroup = 90B608C1283E1110006F4309; packageReferences = ( - 50816B332BC711A800A43F3D /* XCLocalSwiftPackageReference "NodeKitThirdParty" */, + 5097DFAC2BCDFA8300D422EE /* XCLocalSwiftPackageReference "NodeKitThirdParty" */, ); productRefGroup = 90B608CC283E1110006F4309 /* Products */; projectDirPath = ""; @@ -1343,6 +1352,7 @@ files = ( 50816B092BC7078000A43F3D /* Result+Extension.swift in Sources */, 50816B0B2BC7079300A43F3D /* Array+Extension.swift in Sources */, + 5097DF3C2BCD556E00D422EE /* AsyncPagerDataProviderMock.swift in Sources */, 50816B0E2BC7079900A43F3D /* RawEncodableMock.swift in Sources */, 50816B182BC707AE00A43F3D /* AborterMock.swift in Sources */, 50816B0A2BC7079000A43F3D /* DispatchQueue+Extension.swift in Sources */, @@ -1386,6 +1396,7 @@ 90B609BD283E1287006F4309 /* UrlRouting.swift in Sources */, 90B609A1283E1287006F4309 /* UrlDataResponse.swift in Sources */, 90B609AA283E1287006F4309 /* TransportUrlParameters.swift in Sources */, + 5097DF462BCD628400D422EE /* AsyncPagerDataProvider.swift in Sources */, 90B60987283E1287006F4309 /* URLQueryDictionaryKeyEncodingStrategy.swift in Sources */, 90B609BA283E1287006F4309 /* AsyncIterator.swift in Sources */, 90B6099C283E1287006F4309 /* LoadIndicatorNode.swift in Sources */, @@ -1405,6 +1416,7 @@ 90B6098A283E1287006F4309 /* ParametersEncoding.swift in Sources */, 90B60913283E1268006F4309 /* UrlETagSaverNode.swift in Sources */, 90B609BF283E1287006F4309 /* Logable.swift in Sources */, + 5097DFB02BCDFB5200D422EE /* CancellableTask.swift in Sources */, 90B609BB283E1287006F4309 /* StateStorable.swift in Sources */, 90B6098F283E1287006F4309 /* RequestModel.swift in Sources */, 90B6091C283E1268006F4309 /* Array+DtoConvertible.swift in Sources */, @@ -1445,7 +1457,7 @@ 5005EB1C2BB88EC500B670CD /* CombineStreamNode.swift in Sources */, 90B6091A283E1268006F4309 /* RawMappable+Dictionary.swift in Sources */, 90B609A7283E1287006F4309 /* RawEncoderNode.swift in Sources */, - 90B609B9283E1287006F4309 /* OffsetAsyncPager.swift in Sources */, + 90B609B9283E1287006F4309 /* AsyncPagerIterator.swift in Sources */, 50528E272BADF64F00E86CB6 /* NodeResult.swift in Sources */, 90B6090F283E1268006F4309 /* FirstCachePolicyNode.swift in Sources */, 90B60920283E1268006F4309 /* MultipartFileProvider.swift in Sources */, @@ -1526,7 +1538,7 @@ 5060A4792BC402C0004E84E2 /* URLSessionDataTaskActorTests.swift in Sources */, 90B609F6283E16DC006F4309 /* URLQueryInjectorNodeTests.swift in Sources */, 50C8EB4E2BBF1E9700C5CB93 /* HeaderInjectorNodeTests.swift in Sources */, - 50C8EB3C2BBEB5E300C5CB93 /* OffsetAsyncPagerTests.swift in Sources */, + 50C8EB3C2BBEB5E300C5CB93 /* AsyncPagerIteratorTests.swift in Sources */, 502F9D9A2BAA389500151A8D /* LoggingContextTests.swift in Sources */, 90B609FD283E16DC006F4309 /* UrlETagReaderNodeTests.swift in Sources */, 50816AA82BC6FE6200A43F3D /* AuthModelEntry.swift in Sources */, @@ -1923,16 +1935,15 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 50816B332BC711A800A43F3D /* XCLocalSwiftPackageReference "NodeKitThirdParty" */ = { + 5097DFAC2BCDFA8300D422EE /* XCLocalSwiftPackageReference "NodeKitThirdParty" */ = { isa = XCLocalSwiftPackageReference; relativePath = NodeKitThirdParty; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 50816B3C2BC7123A00A43F3D /* NodeKitThirdParty */ = { + 5097DFAD2BCDFA8300D422EE /* NodeKitThirdParty */ = { isa = XCSwiftPackageProductDependency; - package = 50816B332BC711A800A43F3D /* XCLocalSwiftPackageReference "NodeKitThirdParty" */; productName = NodeKitThirdParty; }; /* End XCSwiftPackageProductDependency section */ diff --git a/NodeKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/NodeKit/NodeKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from NodeKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to NodeKit/NodeKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/NodeKit/NodeKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to NodeKit/NodeKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKit.xcscheme b/NodeKit/NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKit.xcscheme similarity index 100% rename from NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKit.xcscheme rename to NodeKit/NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKit.xcscheme diff --git a/NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKitMock.xcscheme b/NodeKit/NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKitMock.xcscheme similarity index 100% rename from NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKitMock.xcscheme rename to NodeKit/NodeKit.xcodeproj/xcshareddata/xcschemes/NodeKitMock.xcscheme diff --git a/NodeKit/CacheNode/ETag/ETagConstants.swift b/NodeKit/NodeKit/CacheNode/ETag/ETagConstants.swift similarity index 100% rename from NodeKit/CacheNode/ETag/ETagConstants.swift rename to NodeKit/NodeKit/CacheNode/ETag/ETagConstants.swift diff --git a/NodeKit/CacheNode/ETag/UrlETagReaderNode.swift b/NodeKit/NodeKit/CacheNode/ETag/UrlETagReaderNode.swift similarity index 100% rename from NodeKit/CacheNode/ETag/UrlETagReaderNode.swift rename to NodeKit/NodeKit/CacheNode/ETag/UrlETagReaderNode.swift diff --git a/NodeKit/CacheNode/ETag/UrlETagSaverNode.swift b/NodeKit/NodeKit/CacheNode/ETag/UrlETagSaverNode.swift similarity index 100% rename from NodeKit/CacheNode/ETag/UrlETagSaverNode.swift rename to NodeKit/NodeKit/CacheNode/ETag/UrlETagSaverNode.swift diff --git a/NodeKit/CacheNode/FirstCachePolicyNode.swift b/NodeKit/NodeKit/CacheNode/FirstCachePolicyNode.swift similarity index 100% rename from NodeKit/CacheNode/FirstCachePolicyNode.swift rename to NodeKit/NodeKit/CacheNode/FirstCachePolicyNode.swift diff --git a/NodeKit/CacheNode/IfServerFailsFromCacheNode.swift b/NodeKit/NodeKit/CacheNode/IfServerFailsFromCacheNode.swift similarity index 100% rename from NodeKit/CacheNode/IfServerFailsFromCacheNode.swift rename to NodeKit/NodeKit/CacheNode/IfServerFailsFromCacheNode.swift diff --git a/NodeKit/CacheNode/UrlCacheReaderNode.swift b/NodeKit/NodeKit/CacheNode/UrlCacheReaderNode.swift similarity index 100% rename from NodeKit/CacheNode/UrlCacheReaderNode.swift rename to NodeKit/NodeKit/CacheNode/UrlCacheReaderNode.swift diff --git a/NodeKit/CacheNode/UrlCacheWriterNode.swift b/NodeKit/NodeKit/CacheNode/UrlCacheWriterNode.swift similarity index 100% rename from NodeKit/CacheNode/UrlCacheWriterNode.swift rename to NodeKit/NodeKit/CacheNode/UrlCacheWriterNode.swift diff --git a/NodeKit/CacheNode/UrlNotModifiedTriggerNode.swift b/NodeKit/NodeKit/CacheNode/UrlNotModifiedTriggerNode.swift similarity index 100% rename from NodeKit/CacheNode/UrlNotModifiedTriggerNode.swift rename to NodeKit/NodeKit/CacheNode/UrlNotModifiedTriggerNode.swift diff --git a/NodeKit/Chains/UrlChainConfigModel.swift b/NodeKit/NodeKit/Chains/UrlChainConfigModel.swift similarity index 100% rename from NodeKit/Chains/UrlChainConfigModel.swift rename to NodeKit/NodeKit/Chains/UrlChainConfigModel.swift diff --git a/NodeKit/Chains/UrlChainsBuilder.swift b/NodeKit/NodeKit/Chains/UrlChainsBuilder.swift similarity index 100% rename from NodeKit/Chains/UrlChainsBuilder.swift rename to NodeKit/NodeKit/Chains/UrlChainsBuilder.swift diff --git a/NodeKit/Chains/UrlServiceChainBuilder.swift b/NodeKit/NodeKit/Chains/UrlServiceChainBuilder.swift similarity index 87% rename from NodeKit/Chains/UrlServiceChainBuilder.swift rename to NodeKit/NodeKit/Chains/UrlServiceChainBuilder.swift index 02be73aa..054f8850 100644 --- a/NodeKit/Chains/UrlServiceChainBuilder.swift +++ b/NodeKit/NodeKit/Chains/UrlServiceChainBuilder.swift @@ -21,7 +21,8 @@ open class UrlServiceChainBuilder { manager: session ) let technicalErrorMapperNode = TechnicaErrorMapperNode(next: requestSenderNode) - return RequestCreatorNode(next: technicalErrorMapperNode, providers: providers) + let aborterNode = AborterNode(next: technicalErrorMapperNode, aborter: requestSenderNode) + return RequestCreatorNode(next: aborterNode, providers: providers) } } diff --git a/NodeKit/Core/Convertion/DTOConvertible.swift b/NodeKit/NodeKit/Core/Convertion/DTOConvertible.swift similarity index 100% rename from NodeKit/Core/Convertion/DTOConvertible.swift rename to NodeKit/NodeKit/Core/Convertion/DTOConvertible.swift diff --git a/NodeKit/Core/Convertion/Extensions/Array+DtoConvertible.swift b/NodeKit/NodeKit/Core/Convertion/Extensions/Array+DtoConvertible.swift similarity index 100% rename from NodeKit/Core/Convertion/Extensions/Array+DtoConvertible.swift rename to NodeKit/NodeKit/Core/Convertion/Extensions/Array+DtoConvertible.swift diff --git a/NodeKit/Core/Convertion/Extensions/Array+RawMappable.swift b/NodeKit/NodeKit/Core/Convertion/Extensions/Array+RawMappable.swift similarity index 100% rename from NodeKit/Core/Convertion/Extensions/Array+RawMappable.swift rename to NodeKit/NodeKit/Core/Convertion/Extensions/Array+RawMappable.swift diff --git a/NodeKit/Core/Convertion/Extensions/DTOConvertible+Dictionary.swift b/NodeKit/NodeKit/Core/Convertion/Extensions/DTOConvertible+Dictionary.swift similarity index 100% rename from NodeKit/Core/Convertion/Extensions/DTOConvertible+Dictionary.swift rename to NodeKit/NodeKit/Core/Convertion/Extensions/DTOConvertible+Dictionary.swift diff --git a/NodeKit/Core/Convertion/Extensions/RawMappable+Dictionary.swift b/NodeKit/NodeKit/Core/Convertion/Extensions/RawMappable+Dictionary.swift similarity index 100% rename from NodeKit/Core/Convertion/Extensions/RawMappable+Dictionary.swift rename to NodeKit/NodeKit/Core/Convertion/Extensions/RawMappable+Dictionary.swift diff --git a/NodeKit/Core/Convertion/Multipart/MultipartFileProvider.swift b/NodeKit/NodeKit/Core/Convertion/Multipart/MultipartFileProvider.swift similarity index 100% rename from NodeKit/Core/Convertion/Multipart/MultipartFileProvider.swift rename to NodeKit/NodeKit/Core/Convertion/Multipart/MultipartFileProvider.swift diff --git a/NodeKit/Core/Convertion/Multipart/MultipartModel + Convertion.swift b/NodeKit/NodeKit/Core/Convertion/Multipart/MultipartModel + Convertion.swift similarity index 100% rename from NodeKit/Core/Convertion/Multipart/MultipartModel + Convertion.swift rename to NodeKit/NodeKit/Core/Convertion/Multipart/MultipartModel + Convertion.swift diff --git a/NodeKit/Core/Convertion/Multipart/MultipartModel.swift b/NodeKit/NodeKit/Core/Convertion/Multipart/MultipartModel.swift similarity index 100% rename from NodeKit/Core/Convertion/Multipart/MultipartModel.swift rename to NodeKit/NodeKit/Core/Convertion/Multipart/MultipartModel.swift diff --git a/NodeKit/Core/Convertion/RawMappable.swift b/NodeKit/NodeKit/Core/Convertion/RawMappable.swift similarity index 100% rename from NodeKit/Core/Convertion/RawMappable.swift rename to NodeKit/NodeKit/Core/Convertion/RawMappable.swift diff --git a/NodeKit/Core/Node/Async/AsyncNode.swift b/NodeKit/NodeKit/Core/Node/Async/AsyncNode.swift similarity index 100% rename from NodeKit/Core/Node/Async/AsyncNode.swift rename to NodeKit/NodeKit/Core/Node/Async/AsyncNode.swift diff --git a/NodeKit/Core/Node/Async/AsyncStreamNode.swift b/NodeKit/NodeKit/Core/Node/Async/AsyncStreamNode.swift similarity index 100% rename from NodeKit/Core/Node/Async/AsyncStreamNode.swift rename to NodeKit/NodeKit/Core/Node/Async/AsyncStreamNode.swift diff --git a/NodeKit/Core/Node/Combine/AsyncCombineNode.swift b/NodeKit/NodeKit/Core/Node/Combine/AsyncCombineNode.swift similarity index 100% rename from NodeKit/Core/Node/Combine/AsyncCombineNode.swift rename to NodeKit/NodeKit/Core/Node/Combine/AsyncCombineNode.swift diff --git a/NodeKit/Core/Node/Combine/AsyncStreamCombineNode.swift b/NodeKit/NodeKit/Core/Node/Combine/AsyncStreamCombineNode.swift similarity index 100% rename from NodeKit/Core/Node/Combine/AsyncStreamCombineNode.swift rename to NodeKit/NodeKit/Core/Node/Combine/AsyncStreamCombineNode.swift diff --git a/NodeKit/Core/Node/Combine/CombineNode.swift b/NodeKit/NodeKit/Core/Node/Combine/CombineNode.swift similarity index 100% rename from NodeKit/Core/Node/Combine/CombineNode.swift rename to NodeKit/NodeKit/Core/Node/Combine/CombineNode.swift diff --git a/NodeKit/Core/Node/Combine/CombineStreamNode.swift b/NodeKit/NodeKit/Core/Node/Combine/CombineStreamNode.swift similarity index 100% rename from NodeKit/Core/Node/Combine/CombineStreamNode.swift rename to NodeKit/NodeKit/Core/Node/Combine/CombineStreamNode.swift diff --git a/NodeKit/Core/Node/LoggableNode.swift b/NodeKit/NodeKit/Core/Node/LoggableNode.swift similarity index 100% rename from NodeKit/Core/Node/LoggableNode.swift rename to NodeKit/NodeKit/Core/Node/LoggableNode.swift diff --git a/NodeKit/Core/Node/NodeResult.swift b/NodeKit/NodeKit/Core/Node/NodeResult.swift similarity index 100% rename from NodeKit/Core/Node/NodeResult.swift rename to NodeKit/NodeKit/Core/Node/NodeResult.swift diff --git a/NodeKit/Encodings/Models/RequestEncodingModel.swift b/NodeKit/NodeKit/Encodings/Models/RequestEncodingModel.swift similarity index 100% rename from NodeKit/Encodings/Models/RequestEncodingModel.swift rename to NodeKit/NodeKit/Encodings/Models/RequestEncodingModel.swift diff --git a/NodeKit/Encodings/ParameterEncoding.swift b/NodeKit/NodeKit/Encodings/ParameterEncoding.swift similarity index 100% rename from NodeKit/Encodings/ParameterEncoding.swift rename to NodeKit/NodeKit/Encodings/ParameterEncoding.swift diff --git a/NodeKit/Encodings/RequestEncodingNodeError.swift b/NodeKit/NodeKit/Encodings/RequestEncodingNodeError.swift similarity index 100% rename from NodeKit/Encodings/RequestEncodingNodeError.swift rename to NodeKit/NodeKit/Encodings/RequestEncodingNodeError.swift diff --git a/NodeKit/Encodings/UrlJsonRequestEncodingNode.swift b/NodeKit/NodeKit/Encodings/UrlJsonRequestEncodingNode.swift similarity index 100% rename from NodeKit/Encodings/UrlJsonRequestEncodingNode.swift rename to NodeKit/NodeKit/Encodings/UrlJsonRequestEncodingNode.swift diff --git a/NodeKit/Info.plist b/NodeKit/NodeKit/Info.plist similarity index 100% rename from NodeKit/Info.plist rename to NodeKit/NodeKit/Info.plist diff --git a/NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift b/NodeKit/NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift similarity index 100% rename from NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift rename to NodeKit/NodeKit/Layers/DTOProcessingLayer/DTOMapperNode.swift diff --git a/NodeKit/Layers/DTOProcessingLayer/RawEncoderNode.swift b/NodeKit/NodeKit/Layers/DTOProcessingLayer/RawEncoderNode.swift similarity index 100% rename from NodeKit/Layers/DTOProcessingLayer/RawEncoderNode.swift rename to NodeKit/NodeKit/Layers/DTOProcessingLayer/RawEncoderNode.swift diff --git a/NodeKit/Layers/DefaultLogOrder.swift b/NodeKit/NodeKit/Layers/DefaultLogOrder.swift similarity index 100% rename from NodeKit/Layers/DefaultLogOrder.swift rename to NodeKit/NodeKit/Layers/DefaultLogOrder.swift diff --git a/NodeKit/Layers/InputProcessingLayer/DTOEncoderNode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/DTOEncoderNode.swift similarity index 100% rename from NodeKit/Layers/InputProcessingLayer/DTOEncoderNode.swift rename to NodeKit/NodeKit/Layers/InputProcessingLayer/DTOEncoderNode.swift diff --git a/NodeKit/Layers/InputProcessingLayer/EntryInputDtoOutputNode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/EntryInputDtoOutputNode.swift similarity index 100% rename from NodeKit/Layers/InputProcessingLayer/EntryInputDtoOutputNode.swift rename to NodeKit/NodeKit/Layers/InputProcessingLayer/EntryInputDtoOutputNode.swift diff --git a/NodeKit/Layers/InputProcessingLayer/ModelInputNode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/ModelInputNode.swift similarity index 100% rename from NodeKit/Layers/InputProcessingLayer/ModelInputNode.swift rename to NodeKit/NodeKit/Layers/InputProcessingLayer/ModelInputNode.swift diff --git a/NodeKit/Layers/InputProcessingLayer/VoidIONode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidIONode.swift similarity index 100% rename from NodeKit/Layers/InputProcessingLayer/VoidIONode.swift rename to NodeKit/NodeKit/Layers/InputProcessingLayer/VoidIONode.swift diff --git a/NodeKit/Layers/InputProcessingLayer/VoidInputNode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidInputNode.swift similarity index 100% rename from NodeKit/Layers/InputProcessingLayer/VoidInputNode.swift rename to NodeKit/NodeKit/Layers/InputProcessingLayer/VoidInputNode.swift diff --git a/NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift b/NodeKit/NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift similarity index 100% rename from NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift rename to NodeKit/NodeKit/Layers/InputProcessingLayer/VoidOutputNode.swift diff --git a/NodeKit/Layers/LayerTypes.swift b/NodeKit/NodeKit/Layers/LayerTypes.swift similarity index 100% rename from NodeKit/Layers/LayerTypes.swift rename to NodeKit/NodeKit/Layers/LayerTypes.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/MetadataConnectorNode.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/MetadataConnectorNode.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/MetadataConnectorNode.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/MetadataConnectorNode.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/Models/EncodableRequestModel.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/EncodableRequestModel.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/Models/EncodableRequestModel.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/EncodableRequestModel.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/Models/Method.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/Method.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/Models/Method.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/Method.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/Models/ParametersEncoding.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/ParametersEncoding.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/Models/ParametersEncoding.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/ParametersEncoding.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/Models/RequestModel.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/RequestModel.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/Models/RequestModel.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/RequestModel.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/Models/RoutableRequestModel.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/RoutableRequestModel.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/Models/RoutableRequestModel.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/RoutableRequestModel.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/Models/URLQueryConfigModel.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/URLQueryConfigModel.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/Models/URLQueryConfigModel.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/Models/URLQueryConfigModel.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/MultipartUrlRequestTrasformatorNode.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/MultipartUrlRequestTrasformatorNode.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/MultipartUrlRequestTrasformatorNode.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/MultipartUrlRequestTrasformatorNode.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/Protocols/UrlRouteProvider.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/Protocols/UrlRouteProvider.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/Protocols/UrlRouteProvider.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/Protocols/UrlRouteProvider.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/RequestEncoderNode.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/RequestEncoderNode.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/RequestEncoderNode.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/RequestEncoderNode.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/RequestRouterNode.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/RequestRouterNode.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/RequestRouterNode.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/RequestRouterNode.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryArrayKeyEncodingStartegy.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryArrayKeyEncodingStartegy.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryArrayKeyEncodingStartegy.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryArrayKeyEncodingStartegy.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryBoolEncodingStartegy.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryBoolEncodingStartegy.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryBoolEncodingStartegy.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryBoolEncodingStartegy.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryDictionaryKeyEncodingStrategy.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryDictionaryKeyEncodingStrategy.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryDictionaryKeyEncodingStrategy.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryEncoding/URLQueryDictionaryKeyEncodingStrategy.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/URLQueryInjectorNode.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryInjectorNode.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/URLQueryInjectorNode.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/URLQueryInjectorNode.swift diff --git a/NodeKit/Layers/RequestBuildingLayer/UrlRequestTrasformatorNode.swift b/NodeKit/NodeKit/Layers/RequestBuildingLayer/UrlRequestTrasformatorNode.swift similarity index 100% rename from NodeKit/Layers/RequestBuildingLayer/UrlRequestTrasformatorNode.swift rename to NodeKit/NodeKit/Layers/RequestBuildingLayer/UrlRequestTrasformatorNode.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/MultipartFormDataFactory.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartFormDataFactory.swift similarity index 100% rename from NodeKit/Layers/RequestProcessingLayer/MultipartFormDataFactory.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartFormDataFactory.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift similarity index 100% rename from NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/MultipartRequestCreatorNode.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift similarity index 100% rename from NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestCreatorNode.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift similarity index 100% rename from NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/RequestSenderNode.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/Support/ParametersEncoding+Alamofire.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/Support/ParametersEncoding+Alamofire.swift similarity index 100% rename from NodeKit/Layers/RequestProcessingLayer/Support/ParametersEncoding+Alamofire.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/Support/ParametersEncoding+Alamofire.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/Support/RawUrlRequest.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/Support/RawUrlRequest.swift similarity index 100% rename from NodeKit/Layers/RequestProcessingLayer/Support/RawUrlRequest.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/Support/RawUrlRequest.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/Support/ServerRequestsManager.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/Support/ServerRequestsManager.swift similarity index 100% rename from NodeKit/Layers/RequestProcessingLayer/Support/ServerRequestsManager.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/Support/ServerRequestsManager.swift diff --git a/NodeKit/Layers/RequestProcessingLayer/URLSessionDataTaskActor.swift b/NodeKit/NodeKit/Layers/RequestProcessingLayer/URLSessionDataTaskActor.swift similarity index 86% rename from NodeKit/Layers/RequestProcessingLayer/URLSessionDataTaskActor.swift rename to NodeKit/NodeKit/Layers/RequestProcessingLayer/URLSessionDataTaskActor.swift index c5be78fd..c17aa581 100644 --- a/NodeKit/Layers/RequestProcessingLayer/URLSessionDataTaskActor.swift +++ b/NodeKit/NodeKit/Layers/RequestProcessingLayer/URLSessionDataTaskActor.swift @@ -8,10 +8,6 @@ import Foundation -public protocol CancellableTask { - func cancel() -} - public protocol URLSessionDataTaskActorProtocol: Actor { func store(task: CancellableTask) func cancelTask() @@ -34,5 +30,3 @@ public actor URLSessionDataTaskActor: URLSessionDataTaskActorProtocol { task = nil } } - -extension URLSessionDataTask: CancellableTask { } diff --git a/NodeKit/Layers/ResponsePostprocessingNode/Models/UrlProcessedResponse.swift b/NodeKit/NodeKit/Layers/ResponsePostprocessingNode/Models/UrlProcessedResponse.swift similarity index 100% rename from NodeKit/Layers/ResponsePostprocessingNode/Models/UrlProcessedResponse.swift rename to NodeKit/NodeKit/Layers/ResponsePostprocessingNode/Models/UrlProcessedResponse.swift diff --git a/NodeKit/Layers/ResponseProcessingLayer/DataLoading/DataLoadingResponseProcessor.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/DataLoading/DataLoadingResponseProcessor.swift similarity index 100% rename from NodeKit/Layers/ResponseProcessingLayer/DataLoading/DataLoadingResponseProcessor.swift rename to NodeKit/NodeKit/Layers/ResponseProcessingLayer/DataLoading/DataLoadingResponseProcessor.swift diff --git a/NodeKit/Layers/ResponseProcessingLayer/Models/UrlDataResponse.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/Models/UrlDataResponse.swift similarity index 100% rename from NodeKit/Layers/ResponseProcessingLayer/Models/UrlDataResponse.swift rename to NodeKit/NodeKit/Layers/ResponseProcessingLayer/Models/UrlDataResponse.swift diff --git a/NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift similarity index 100% rename from NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift rename to NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataParserNode.swift diff --git a/NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift similarity index 100% rename from NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift rename to NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseDataPreprocessorNode.swift diff --git a/NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift similarity index 100% rename from NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift rename to NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseHttpErrorProcessorNode.swift diff --git a/NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift b/NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift similarity index 100% rename from NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift rename to NodeKit/NodeKit/Layers/ResponseProcessingLayer/ResponseProcessorNode.swift diff --git a/NodeKit/Layers/TrasportLayer/Models/TransportUrlParameters.swift b/NodeKit/NodeKit/Layers/TrasportLayer/Models/TransportUrlParameters.swift similarity index 100% rename from NodeKit/Layers/TrasportLayer/Models/TransportUrlParameters.swift rename to NodeKit/NodeKit/Layers/TrasportLayer/Models/TransportUrlParameters.swift diff --git a/NodeKit/Layers/TrasportLayer/Models/TransportUrlRequest.swift b/NodeKit/NodeKit/Layers/TrasportLayer/Models/TransportUrlRequest.swift similarity index 100% rename from NodeKit/Layers/TrasportLayer/Models/TransportUrlRequest.swift rename to NodeKit/NodeKit/Layers/TrasportLayer/Models/TransportUrlRequest.swift diff --git a/NodeKit/Layers/TrasportLayer/TechnicaErrorMapperNode.swift b/NodeKit/NodeKit/Layers/TrasportLayer/TechnicaErrorMapperNode.swift similarity index 100% rename from NodeKit/Layers/TrasportLayer/TechnicaErrorMapperNode.swift rename to NodeKit/NodeKit/Layers/TrasportLayer/TechnicaErrorMapperNode.swift diff --git a/NodeKit/Layers/Utils/AccessSafe/AccessSafeNode.swift b/NodeKit/NodeKit/Layers/Utils/AccessSafe/AccessSafeNode.swift similarity index 100% rename from NodeKit/Layers/Utils/AccessSafe/AccessSafeNode.swift rename to NodeKit/NodeKit/Layers/Utils/AccessSafe/AccessSafeNode.swift diff --git a/NodeKit/Layers/Utils/AccessSafe/TokenRefresherActor.swift b/NodeKit/NodeKit/Layers/Utils/AccessSafe/TokenRefresherActor.swift similarity index 100% rename from NodeKit/Layers/Utils/AccessSafe/TokenRefresherActor.swift rename to NodeKit/NodeKit/Layers/Utils/AccessSafe/TokenRefresherActor.swift diff --git a/NodeKit/Layers/Utils/AccessSafe/TokenRefresherNode.swift b/NodeKit/NodeKit/Layers/Utils/AccessSafe/TokenRefresherNode.swift similarity index 100% rename from NodeKit/Layers/Utils/AccessSafe/TokenRefresherNode.swift rename to NodeKit/NodeKit/Layers/Utils/AccessSafe/TokenRefresherNode.swift diff --git a/NodeKit/Layers/Utils/HeaderInjectorNode.swift b/NodeKit/NodeKit/Layers/Utils/HeaderInjectorNode.swift similarity index 100% rename from NodeKit/Layers/Utils/HeaderInjectorNode.swift rename to NodeKit/NodeKit/Layers/Utils/HeaderInjectorNode.swift diff --git a/NodeKit/Layers/Utils/LoadIndicatorNode.swift b/NodeKit/NodeKit/Layers/Utils/LoadIndicatorNode.swift similarity index 100% rename from NodeKit/Layers/Utils/LoadIndicatorNode.swift rename to NodeKit/NodeKit/Layers/Utils/LoadIndicatorNode.swift diff --git a/NodeKit/Layers/Utils/RequestAborterNode.swift b/NodeKit/NodeKit/Layers/Utils/RequestAborterNode.swift similarity index 100% rename from NodeKit/Layers/Utils/RequestAborterNode.swift rename to NodeKit/NodeKit/Layers/Utils/RequestAborterNode.swift diff --git a/NodeKit/MockerIntegration/MockerProxyConfigNode.swift b/NodeKit/NodeKit/MockerIntegration/MockerProxyConfigNode.swift similarity index 100% rename from NodeKit/MockerIntegration/MockerProxyConfigNode.swift rename to NodeKit/NodeKit/MockerIntegration/MockerProxyConfigNode.swift diff --git a/NodeKit/NodeKit.h b/NodeKit/NodeKit/NodeKit.h similarity index 100% rename from NodeKit/NodeKit.h rename to NodeKit/NodeKit/NodeKit.h diff --git a/NodeKit/Utils/AsyncIterator/AsyncIterator.swift b/NodeKit/NodeKit/Utils/AsyncIterator/AsyncIterator.swift similarity index 51% rename from NodeKit/Utils/AsyncIterator/AsyncIterator.swift rename to NodeKit/NodeKit/Utils/AsyncIterator/AsyncIterator.swift index 74dbd736..91601b3d 100644 --- a/NodeKit/Utils/AsyncIterator/AsyncIterator.swift +++ b/NodeKit/NodeKit/Utils/AsyncIterator/AsyncIterator.swift @@ -5,11 +5,16 @@ /// Интерфейс любого асинхронно интерируемого компонента /// /// pageSize, offset и другие параметры указываются в конкретной реализации протокола -public protocol AsyncIterator: Actor { +public protocol AsyncIterator: Actor { associatedtype Value - func next() -> Result<(data: Value, end: Bool), Error> + /// Запрос следующих данных + @discardableResult + func next() async -> Result + + /// Показывает есть ли еще данные + func hasNext() -> Bool - /// Сбрасывает свойства итератора + /// Сброс свойств итератора func renew() } diff --git a/NodeKit/NodeKit/Utils/AsyncIterator/AsyncPagerDataProvider.swift b/NodeKit/NodeKit/Utils/AsyncIterator/AsyncPagerDataProvider.swift new file mode 100644 index 00000000..9fd5d6c0 --- /dev/null +++ b/NodeKit/NodeKit/Utils/AsyncIterator/AsyncPagerDataProvider.swift @@ -0,0 +1,33 @@ +// +// AsyncPagerDataProvider.swift +// NodeKit +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Surf. All rights reserved. +// + +import Foundation + +/// Результат запроса ``AsyncPagerDataProvider``. +public struct AsyncPagerData { + public let value: Value + public let len: Int + + public init(value: Value, len: Int) { + self.value = value + self.len = len + } +} + +/// Протокол описывающий провайдера данных, который возвращает результат работы цепочки или узла. +public protocol AsyncPagerDataProvider { + associatedtype Value + + /// Метод запроса данных. + /// + /// - Parameters: + /// - index: Индекс с которого будут запрошены данные. + /// - pageSize: Количество элементов на странице. + /// - Returns: Результат работыу цепочки или узла. + func provide(for index: Int, with pageSize: Int) async -> NodeResult> +} diff --git a/NodeKit/NodeKit/Utils/AsyncIterator/AsyncPagerIterator.swift b/NodeKit/NodeKit/Utils/AsyncIterator/AsyncPagerIterator.swift new file mode 100644 index 00000000..578e3681 --- /dev/null +++ b/NodeKit/NodeKit/Utils/AsyncIterator/AsyncPagerIterator.swift @@ -0,0 +1,70 @@ +// +// AsyncPagerIterator.swift +// NodeKit +// + +/// Предоставляет возможность делать пагинацию на оффсетах +public actor AsyncPagerIterator: AsyncIterator, StateStorable { + + // MARK: - Nested Types + + private struct PagerState { + var index: Int + var pageSize: Int + var hasNext: Bool + } + + // MARK: - Private Properties + + private let dataProvider: any AsyncPagerDataProvider + private var currentState: PagerState + private var statesStore = [PagerState]() + + // MARK: - Initialization + + public init(dataProvider: any AsyncPagerDataProvider, pageSize: Int) { + self.dataProvider = dataProvider + self.currentState = PagerState(index: 0, pageSize: pageSize, hasNext: true) + } + + // MARK: - AsyncIterator + + /// Запрашивает данные у провайдера и при успешном результате обновляет состояние. + @discardableResult + public func next() async -> Result { + return await dataProvider.provide(for: currentState.index, with: currentState.pageSize) + .flatMap { data in + currentState.index += data.len + currentState.hasNext = data.len != 0 && data.len >= currentState.pageSize + return .success(data.value) + } + } + + /// Возвращает есть ли еще данные для текущего состояния. + public func hasNext() -> Bool { + return currentState.hasNext + } + + /// Сбрасывает текущее состояние. + public func renew() { + currentState.index = 0 + currentState.hasNext = true + } + + // MARK: - StateStorable + + /// Добавляет текущее состояние в список сохраненных. + public func saveState() { + statesStore.append(currentState) + } + + /// Удаляет все сохарненные состояния. + public func clearStates() { + statesStore.removeAll() + } + + /// Меняет текущее состояние на последнее сохраненное, удаляя его из списка сохранненых. + public func restoreState() { + currentState = statesStore.last != nil ? statesStore.removeLast() : currentState + } +} diff --git a/NodeKit/Utils/AsyncIterator/StateStorable.swift b/NodeKit/NodeKit/Utils/AsyncIterator/StateStorable.swift similarity index 59% rename from NodeKit/Utils/AsyncIterator/StateStorable.swift rename to NodeKit/NodeKit/Utils/AsyncIterator/StateStorable.swift index b6558c20..bbc05353 100644 --- a/NodeKit/Utils/AsyncIterator/StateStorable.swift +++ b/NodeKit/NodeKit/Utils/AsyncIterator/StateStorable.swift @@ -3,12 +3,6 @@ // NodeKit // -protocol StateStorableLegacy { - func saveState() - func clearStates() - func restoreState() -} - public protocol StateStorable: Actor { func saveState() func clearStates() diff --git a/NodeKit/NodeKit/Utils/CancellableTask.swift b/NodeKit/NodeKit/Utils/CancellableTask.swift new file mode 100644 index 00000000..6a475f98 --- /dev/null +++ b/NodeKit/NodeKit/Utils/CancellableTask.swift @@ -0,0 +1,15 @@ +// +// CancellableTask.swift +// +// +// Created by Andrei Frolov on 15.04.24. +// + +import Foundation + +public protocol CancellableTask { + func cancel() +} + +extension Task: CancellableTask { } +extension URLSessionDataTask: CancellableTask { } diff --git a/NodeKit/Utils/Logging/Log.swift b/NodeKit/NodeKit/Utils/Logging/Log.swift similarity index 100% rename from NodeKit/Utils/Logging/Log.swift rename to NodeKit/NodeKit/Utils/Logging/Log.swift diff --git a/NodeKit/Utils/Logging/Logable.swift b/NodeKit/NodeKit/Utils/Logging/Logable.swift similarity index 100% rename from NodeKit/Utils/Logging/Logable.swift rename to NodeKit/NodeKit/Utils/Logging/Logable.swift diff --git a/NodeKit/Utils/Logging/LoggerExtensions.swift b/NodeKit/NodeKit/Utils/Logging/LoggerExtensions.swift similarity index 100% rename from NodeKit/Utils/Logging/LoggerExtensions.swift rename to NodeKit/NodeKit/Utils/Logging/LoggerExtensions.swift diff --git a/NodeKit/Utils/Logging/LoggerNode.swift b/NodeKit/NodeKit/Utils/Logging/LoggerNode.swift similarity index 100% rename from NodeKit/Utils/Logging/LoggerNode.swift rename to NodeKit/NodeKit/Utils/Logging/LoggerNode.swift diff --git a/NodeKit/Utils/Logging/LoggerStreamNode.swift b/NodeKit/NodeKit/Utils/Logging/LoggerStreamNode.swift similarity index 100% rename from NodeKit/Utils/Logging/LoggerStreamNode.swift rename to NodeKit/NodeKit/Utils/Logging/LoggerStreamNode.swift diff --git a/NodeKit/Utils/Logging/LoggingContext.swift b/NodeKit/NodeKit/Utils/Logging/LoggingContext.swift similarity index 100% rename from NodeKit/Utils/Logging/LoggingContext.swift rename to NodeKit/NodeKit/Utils/Logging/LoggingContext.swift diff --git a/NodeKit/Utils/Mapping/CodableRawMapper.swift b/NodeKit/NodeKit/Utils/Mapping/CodableRawMapper.swift similarity index 100% rename from NodeKit/Utils/Mapping/CodableRawMapper.swift rename to NodeKit/NodeKit/Utils/Mapping/CodableRawMapper.swift diff --git a/NodeKit/Utils/MetadataProvider.swift b/NodeKit/NodeKit/Utils/MetadataProvider.swift similarity index 100% rename from NodeKit/Utils/MetadataProvider.swift rename to NodeKit/NodeKit/Utils/MetadataProvider.swift diff --git a/NodeKit/Utils/UrlRouting/UrlRouting.swift b/NodeKit/NodeKit/Utils/UrlRouting/UrlRouting.swift similarity index 100% rename from NodeKit/Utils/UrlRouting/UrlRouting.swift rename to NodeKit/NodeKit/Utils/UrlRouting/UrlRouting.swift diff --git a/NodeKitMock/AborterMock.swift b/NodeKit/NodeKitMock/AborterMock.swift similarity index 100% rename from NodeKitMock/AborterMock.swift rename to NodeKit/NodeKitMock/AborterMock.swift diff --git a/NodeKitMock/AsyncNodeMock.swift b/NodeKit/NodeKitMock/AsyncNodeMock.swift similarity index 100% rename from NodeKitMock/AsyncNodeMock.swift rename to NodeKit/NodeKitMock/AsyncNodeMock.swift diff --git a/NodeKit/NodeKitMock/AsyncPagerDataProviderMock.swift b/NodeKit/NodeKitMock/AsyncPagerDataProviderMock.swift new file mode 100644 index 00000000..58a87f6c --- /dev/null +++ b/NodeKit/NodeKitMock/AsyncPagerDataProviderMock.swift @@ -0,0 +1,26 @@ +// +// AsyncPagerDataProviderMock.swift +// NodeKitMock +// +// Created by Andrei Frolov on 15.04.24. +// Copyright © 2024 Surf. All rights reserved. +// + +@testable import NodeKit + +public class AsyncPagerDataProviderMock: AsyncPagerDataProvider { + + public var invokedProvide = false + public var invokedProvideCount = 0 + public var invokedProvideParameters: (index: Int, pageSize: Int)? + public var invokedProvideParametersList: [(index: Int, pageSize: Int)] = [] + public var stubbedProvideResult: NodeResult>! + + public func provide(for index: Int, with pageSize: Int) async -> NodeResult> { + invokedProvide = true + invokedProvideCount += 1 + invokedProvideParameters = (index, pageSize) + invokedProvideParametersList.append((index, pageSize)) + return stubbedProvideResult + } +} diff --git a/NodeKitMock/AsyncStreamNodeMock.swift b/NodeKit/NodeKitMock/AsyncStreamNodeMock.swift similarity index 100% rename from NodeKitMock/AsyncStreamNodeMock.swift rename to NodeKit/NodeKitMock/AsyncStreamNodeMock.swift diff --git a/NodeKitMock/CancellableTaskMock.swift b/NodeKit/NodeKitMock/CancellableTaskMock.swift similarity index 100% rename from NodeKitMock/CancellableTaskMock.swift rename to NodeKit/NodeKitMock/CancellableTaskMock.swift diff --git a/NodeKitMock/CombineNodeMock.swift b/NodeKit/NodeKitMock/CombineNodeMock.swift similarity index 100% rename from NodeKitMock/CombineNodeMock.swift rename to NodeKit/NodeKitMock/CombineNodeMock.swift diff --git a/NodeKitMock/CombineStreamNodeMock.swift b/NodeKit/NodeKitMock/CombineStreamNodeMock.swift similarity index 100% rename from NodeKitMock/CombineStreamNodeMock.swift rename to NodeKit/NodeKitMock/CombineStreamNodeMock.swift diff --git a/NodeKitMock/DTOConvertibleMock.swift b/NodeKit/NodeKitMock/DTOConvertibleMock.swift similarity index 100% rename from NodeKitMock/DTOConvertibleMock.swift rename to NodeKit/NodeKitMock/DTOConvertibleMock.swift diff --git a/NodeKitMock/DTODecodableMock.swift b/NodeKit/NodeKitMock/DTODecodableMock.swift similarity index 100% rename from NodeKitMock/DTODecodableMock.swift rename to NodeKit/NodeKitMock/DTODecodableMock.swift diff --git a/NodeKitMock/DTOEncodableMock.swift b/NodeKit/NodeKitMock/DTOEncodableMock.swift similarity index 100% rename from NodeKitMock/DTOEncodableMock.swift rename to NodeKit/NodeKitMock/DTOEncodableMock.swift diff --git a/NodeKitMock/LoggingContextMock.swift b/NodeKit/NodeKitMock/LoggingContextMock.swift similarity index 100% rename from NodeKitMock/LoggingContextMock.swift rename to NodeKit/NodeKitMock/LoggingContextMock.swift diff --git a/NodeKitMock/MetadataProviderMock.swift b/NodeKit/NodeKitMock/MetadataProviderMock.swift similarity index 100% rename from NodeKitMock/MetadataProviderMock.swift rename to NodeKit/NodeKitMock/MetadataProviderMock.swift diff --git a/NodeKitMock/MockError.swift b/NodeKit/NodeKitMock/MockError.swift similarity index 100% rename from NodeKitMock/MockError.swift rename to NodeKit/NodeKitMock/MockError.swift diff --git a/NodeKitMock/MultipartFormDataFactoryMock.swift b/NodeKit/NodeKitMock/MultipartFormDataFactoryMock.swift similarity index 100% rename from NodeKitMock/MultipartFormDataFactoryMock.swift rename to NodeKit/NodeKitMock/MultipartFormDataFactoryMock.swift diff --git a/NodeKitMock/MultipartFormDataMock.swift b/NodeKit/NodeKitMock/MultipartFormDataMock.swift similarity index 100% rename from NodeKitMock/MultipartFormDataMock.swift rename to NodeKit/NodeKitMock/MultipartFormDataMock.swift diff --git a/NodeKitMock/NetworkMock.swift b/NodeKit/NodeKitMock/NetworkMock.swift similarity index 100% rename from NodeKitMock/NetworkMock.swift rename to NodeKit/NodeKitMock/NetworkMock.swift diff --git a/NodeKitMock/RawDecodableMock.swift b/NodeKit/NodeKitMock/RawDecodableMock.swift similarity index 100% rename from NodeKitMock/RawDecodableMock.swift rename to NodeKit/NodeKitMock/RawDecodableMock.swift diff --git a/NodeKitMock/RawEncodableMock.swift b/NodeKit/NodeKitMock/RawEncodableMock.swift similarity index 100% rename from NodeKitMock/RawEncodableMock.swift rename to NodeKit/NodeKitMock/RawEncodableMock.swift diff --git a/NodeKitMock/RawMappableMock.swift b/NodeKit/NodeKitMock/RawMappableMock.swift similarity index 100% rename from NodeKitMock/RawMappableMock.swift rename to NodeKit/NodeKitMock/RawMappableMock.swift diff --git a/NodeKitMock/TokenRefresherActorMock.swift b/NodeKit/NodeKitMock/TokenRefresherActorMock.swift similarity index 100% rename from NodeKitMock/TokenRefresherActorMock.swift rename to NodeKit/NodeKitMock/TokenRefresherActorMock.swift diff --git a/NodeKitMock/URLProtocolMock.swift b/NodeKit/NodeKitMock/URLProtocolMock.swift similarity index 100% rename from NodeKitMock/URLProtocolMock.swift rename to NodeKit/NodeKitMock/URLProtocolMock.swift diff --git a/NodeKitMock/URLSessionDataTaskActorMock.swift b/NodeKit/NodeKitMock/URLSessionDataTaskActorMock.swift similarity index 100% rename from NodeKitMock/URLSessionDataTaskActorMock.swift rename to NodeKit/NodeKitMock/URLSessionDataTaskActorMock.swift diff --git a/NodeKitMock/URLSessionDataTaskMock.swift b/NodeKit/NodeKitMock/URLSessionDataTaskMock.swift similarity index 100% rename from NodeKitMock/URLSessionDataTaskMock.swift rename to NodeKit/NodeKitMock/URLSessionDataTaskMock.swift diff --git a/NodeKitMock/UrlRouteProviderMock.swift b/NodeKit/NodeKitMock/UrlRouteProviderMock.swift similarity index 100% rename from NodeKitMock/UrlRouteProviderMock.swift rename to NodeKit/NodeKitMock/UrlRouteProviderMock.swift diff --git a/NodeKitMock/UrlServiceChainBuilderMock.swift b/NodeKit/NodeKitMock/UrlServiceChainBuilderMock.swift similarity index 100% rename from NodeKitMock/UrlServiceChainBuilderMock.swift rename to NodeKit/NodeKitMock/UrlServiceChainBuilderMock.swift diff --git a/NodeKitMock/Utils/Array+Extension.swift b/NodeKit/NodeKitMock/Utils/Array+Extension.swift similarity index 100% rename from NodeKitMock/Utils/Array+Extension.swift rename to NodeKit/NodeKitMock/Utils/Array+Extension.swift diff --git a/NodeKitMock/Utils/DispatchQueue+Extension.swift b/NodeKit/NodeKitMock/Utils/DispatchQueue+Extension.swift similarity index 100% rename from NodeKitMock/Utils/DispatchQueue+Extension.swift rename to NodeKit/NodeKitMock/Utils/DispatchQueue+Extension.swift diff --git a/NodeKitMock/Utils/Equatable/Log+Equatalbe.swift b/NodeKit/NodeKitMock/Utils/Equatable/Log+Equatalbe.swift similarity index 100% rename from NodeKitMock/Utils/Equatable/Log+Equatalbe.swift rename to NodeKit/NodeKitMock/Utils/Equatable/Log+Equatalbe.swift diff --git a/NodeKitMock/Utils/Equatable/TransportUrlRequest+Equatable.swift b/NodeKit/NodeKitMock/Utils/Equatable/TransportUrlRequest+Equatable.swift similarity index 100% rename from NodeKitMock/Utils/Equatable/TransportUrlRequest+Equatable.swift rename to NodeKit/NodeKitMock/Utils/Equatable/TransportUrlRequest+Equatable.swift diff --git a/NodeKitMock/Utils/Result+Extension.swift b/NodeKit/NodeKitMock/Utils/Result+Extension.swift similarity index 100% rename from NodeKitMock/Utils/Result+Extension.swift rename to NodeKit/NodeKitMock/Utils/Result+Extension.swift diff --git a/NodeKitTests/IntegrationTests/EmptyResponseMappingTests.swift b/NodeKit/NodeKitTests/IntegrationTests/EmptyResponseMappingTests.swift similarity index 100% rename from NodeKitTests/IntegrationTests/EmptyResponseMappingTests.swift rename to NodeKit/NodeKitTests/IntegrationTests/EmptyResponseMappingTests.swift diff --git a/NodeKitTests/IntegrationTests/FromURLCodingTests.swift b/NodeKit/NodeKitTests/IntegrationTests/FromURLCodingTests.swift similarity index 100% rename from NodeKitTests/IntegrationTests/FromURLCodingTests.swift rename to NodeKit/NodeKitTests/IntegrationTests/FromURLCodingTests.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Infrastructure.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Infrastructure.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Infrastructure.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Infrastructure.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/AuthModel.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/AuthModel.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/AuthModel.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/AuthModel.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/Credentials.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/Credentials.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/Credentials.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/Credentials.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/User.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/User.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/User.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entity/User.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/AuthModelEntry.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/AuthModelEntry.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/AuthModelEntry.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/AuthModelEntry.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/CredentialsEntry.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/CredentialsEntry.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/CredentialsEntry.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/CredentialsEntry.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/UserEntry.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/UserEntry.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/UserEntry.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Models/Entry/UserEntry.swift diff --git a/NodeKitTests/IntegrationTests/Infrastructure/Nodes/CustomServerErrorProcessorNode.swift b/NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Nodes/CustomServerErrorProcessorNode.swift similarity index 100% rename from NodeKitTests/IntegrationTests/Infrastructure/Nodes/CustomServerErrorProcessorNode.swift rename to NodeKit/NodeKitTests/IntegrationTests/Infrastructure/Nodes/CustomServerErrorProcessorNode.swift diff --git a/NodeKitTests/IntegrationTests/MultipartRequestTests.swift b/NodeKit/NodeKitTests/IntegrationTests/MultipartRequestTests.swift similarity index 100% rename from NodeKitTests/IntegrationTests/MultipartRequestTests.swift rename to NodeKit/NodeKitTests/IntegrationTests/MultipartRequestTests.swift diff --git a/NodeKitTests/IntegrationTests/SimpleURLChainTests.swift b/NodeKit/NodeKitTests/IntegrationTests/SimpleURLChainTests.swift similarity index 100% rename from NodeKitTests/IntegrationTests/SimpleURLChainTests.swift rename to NodeKit/NodeKitTests/IntegrationTests/SimpleURLChainTests.swift diff --git a/NodeKitTests/IntegrationTests/URLResponsesStub.swift b/NodeKit/NodeKitTests/IntegrationTests/URLResponsesStub.swift similarity index 100% rename from NodeKitTests/IntegrationTests/URLResponsesStub.swift rename to NodeKit/NodeKitTests/IntegrationTests/URLResponsesStub.swift diff --git a/NodeKitTests/Resources/LICENSE.txt b/NodeKit/NodeKitTests/Resources/LICENSE.txt similarity index 100% rename from NodeKitTests/Resources/LICENSE.txt rename to NodeKit/NodeKitTests/Resources/LICENSE.txt diff --git a/NodeKit/NodeKitTests/UnitTests/AsyncIterator/AsyncPagerIteratorTests.swift b/NodeKit/NodeKitTests/UnitTests/AsyncIterator/AsyncPagerIteratorTests.swift new file mode 100644 index 00000000..d3ead35c --- /dev/null +++ b/NodeKit/NodeKitTests/UnitTests/AsyncIterator/AsyncPagerIteratorTests.swift @@ -0,0 +1,286 @@ +// +// AsyncPagerIteratorTests.swift +// NodeKitTests +// +// Created by Andrei Frolov on 04.04.24. +// Copyright © 2024 Surf. All rights reserved. +// + +@testable import NodeKit +@testable import NodeKitMock + +import XCTest + +final class AsyncPagerIteratorTests: XCTestCase { + + // MARK: - Dependencies + + private var pageSize: Int = 5 + private var dataProviderMock: AsyncPagerDataProviderMock<[String]>! + + // MARK: - Sut + + private var sut: AsyncPagerIterator<[String]>! + + // MARK: - Lifecycle + + override func setUp() { + super.setUp() + dataProviderMock = AsyncPagerDataProviderMock() + sut = AsyncPagerIterator(dataProvider: dataProviderMock, pageSize: pageSize) + } + + override func tearDown() { + super.tearDown() + dataProviderMock = nil + sut = nil + } + + // MARK: - Tests + + func testNext_whenSuccess_thenDataProviderCalled() async throws { + // given + + let expectedIndex = 0 + + dataProviderMock.stubbedProvideResult = .success(.init(value: [], len: 1)) + + // when + + await sut.next() + + // then + + let parameters = try XCTUnwrap(dataProviderMock.invokedProvideParameters) + + XCTAssertEqual(dataProviderMock.invokedProvideCount, 1) + XCTAssertEqual(parameters.index, expectedIndex) + XCTAssertEqual(parameters.pageSize, pageSize) + } + + func testNext_whenCalledTwoTimes_withSuccess_thenDataProviderCalled() async throws { + // given + + let expectedIndex = 0 + + dataProviderMock.stubbedProvideResult = .success(.init(value: [], len: 5)) + + // when + + await sut.next() + await sut.next() + + // then + + let firstCallParameters = try XCTUnwrap(dataProviderMock.invokedProvideParametersList.safe(index: 0)) + let secondCallParameters = try XCTUnwrap(dataProviderMock.invokedProvideParametersList.safe(index: 1)) + + XCTAssertEqual(dataProviderMock.invokedProvideCount, 2) + XCTAssertEqual(firstCallParameters.index, expectedIndex) + XCTAssertEqual(firstCallParameters.pageSize, pageSize) + XCTAssertEqual(secondCallParameters.index, expectedIndex + pageSize) + XCTAssertEqual(secondCallParameters.pageSize, pageSize) + } + + func testNext_whenSuccess_thenSuccessReceived() async throws { + // given + + let expectedArr = ["1", "2", "3", "4", "5"] + + dataProviderMock.stubbedProvideResult = .success(.init(value: expectedArr, len: expectedArr.count)) + + // when + + let result = await sut.next() + + // then + + let unwrappedResult = try XCTUnwrap(result.value) + + XCTAssertEqual(unwrappedResult, expectedArr) + } + + func testNext_whenFailure_thenFailureReceived() async throws { + // given + + dataProviderMock.stubbedProvideResult = .failure(MockError.firstError) + + // when + + let result = await sut.next() + + // then + + let error = try XCTUnwrap(result.error as? MockError) + + XCTAssertEqual(error, .firstError) + } + + func testHasNext_whenFullArrayReceived_thenHasNext() async throws { + // given + + dataProviderMock.stubbedProvideResult = .success(.init( + value: ["1", "2", "3", "4", "5"], + len: 5 + )) + + // when + + await sut.next() + let hasNext = await sut.hasNext() + + // then + + XCTAssertTrue(hasNext) + } + + func testHasNext_whenEmptyArrayReceived_thenHasNotNext() async throws { + // given + + dataProviderMock.stubbedProvideResult = .success(.init( + value: [], + len: 0 + )) + + // when + + await sut.next() + let hasNext = await sut.hasNext() + + // then + + XCTAssertFalse(hasNext) + } + + func testHasNext_whenArrayWithSizeLessThanPageSizeReceived_thenHasNotNext() async throws { + // given + + dataProviderMock.stubbedProvideResult = .success(.init( + value: [], + len: 3 + )) + + // when + + await sut.next() + let hasNext = await sut.hasNext() + + // then + + XCTAssertFalse(hasNext) + } + + func testHasNext_whenFailure_thenHasNext() async throws { + // given + + dataProviderMock.stubbedProvideResult = .failure(MockError.firstError) + + // when + + await sut.next() + let hasNext = await sut.hasNext() + + // then + + XCTAssertTrue(hasNext) + } + + func testRenew_thenZeroIndexReceived() async throws { + // given + + let expectedArr = ["1", "2", "3", "4", "5"] + + dataProviderMock.stubbedProvideResult = .success(.init(value: expectedArr, len: expectedArr.count)) + + // when + + await sut.next() + await sut.next() + await sut.renew() + await sut.next() + + // then + + let indexes = dataProviderMock.invokedProvideParametersList.map { $0.index } + let pageSizes = dataProviderMock.invokedProvideParametersList.map { $0.pageSize } + + XCTAssertEqual(indexes, [0, 5, 0]) + XCTAssertEqual(pageSizes, [5, 5, 5]) + } + + func testSaveState_thenIteratorStartFromSavedState() async throws { + // given + + let expectedArr = ["1", "2", "3", "4", "5"] + + dataProviderMock.stubbedProvideResult = .success(.init(value: expectedArr, len: expectedArr.count)) + + // when + + await sut.saveState() + await sut.next() + await sut.next() + await sut.restoreState() + await sut.next() + + // then + + let indexes = dataProviderMock.invokedProvideParametersList.map { $0.index } + let pageSizes = dataProviderMock.invokedProvideParametersList.map { $0.pageSize } + + XCTAssertEqual(indexes, [0, 5, 0]) + XCTAssertEqual(pageSizes, [5, 5, 5]) + } + + func testSaveState_whenSaveTwoStates_thenIteratorStartFromSavedState() async throws { + // given + + let expectedArr = ["1", "2", "3", "4", "5"] + + dataProviderMock.stubbedProvideResult = .success(.init(value: expectedArr, len: expectedArr.count)) + + // when + + await sut.saveState() + await sut.next() + await sut.saveState() + await sut.next() + await sut.restoreState() + await sut.next() + await sut.restoreState() + await sut.next() + + // then + + let indexes = dataProviderMock.invokedProvideParametersList.map { $0.index } + let pageSizes = dataProviderMock.invokedProvideParametersList.map { $0.pageSize } + + XCTAssertEqual(indexes, [0, 5, 5, 0]) + XCTAssertEqual(pageSizes, [5, 5, 5, 5]) + } + + func testClearState_thenSavedStateCleared() async throws { + // given + + let expectedArr = ["1", "2", "3", "4", "5"] + + dataProviderMock.stubbedProvideResult = .success(.init(value: expectedArr, len: expectedArr.count)) + + // when + + await sut.saveState() + await sut.next() + await sut.next() + await sut.clearStates() + await sut.restoreState() + await sut.next() + + // then + + let indexes = dataProviderMock.invokedProvideParametersList.map { $0.index } + let pageSizes = dataProviderMock.invokedProvideParametersList.map { $0.pageSize } + + XCTAssertEqual(indexes, [0, 5, 10]) + XCTAssertEqual(pageSizes, [5, 5, 5]) + } +} diff --git a/NodeKitTests/UnitTests/Cache/ETag/TestUtls.swift b/NodeKit/NodeKitTests/UnitTests/Cache/ETag/TestUtls.swift similarity index 100% rename from NodeKitTests/UnitTests/Cache/ETag/TestUtls.swift rename to NodeKit/NodeKitTests/UnitTests/Cache/ETag/TestUtls.swift diff --git a/NodeKitTests/UnitTests/Cache/ETag/UrlWithOrderedQuery.swift b/NodeKit/NodeKitTests/UnitTests/Cache/ETag/UrlWithOrderedQuery.swift similarity index 100% rename from NodeKitTests/UnitTests/Cache/ETag/UrlWithOrderedQuery.swift rename to NodeKit/NodeKitTests/UnitTests/Cache/ETag/UrlWithOrderedQuery.swift diff --git a/NodeKitTests/UnitTests/Cache/FirstCachePolicyTests.swift b/NodeKit/NodeKitTests/UnitTests/Cache/FirstCachePolicyTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Cache/FirstCachePolicyTests.swift rename to NodeKit/NodeKitTests/UnitTests/Cache/FirstCachePolicyTests.swift diff --git a/NodeKitTests/UnitTests/Coding/ArrayDTODecodableTests.swift b/NodeKit/NodeKitTests/UnitTests/Coding/ArrayDTODecodableTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Coding/ArrayDTODecodableTests.swift rename to NodeKit/NodeKitTests/UnitTests/Coding/ArrayDTODecodableTests.swift diff --git a/NodeKitTests/UnitTests/Coding/ArrayRawMappableTests.swift b/NodeKit/NodeKitTests/UnitTests/Coding/ArrayRawMappableTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Coding/ArrayRawMappableTests.swift rename to NodeKit/NodeKitTests/UnitTests/Coding/ArrayRawMappableTests.swift diff --git a/NodeKitTests/UnitTests/Coding/DTODecodableTests.swift b/NodeKit/NodeKitTests/UnitTests/Coding/DTODecodableTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Coding/DTODecodableTests.swift rename to NodeKit/NodeKitTests/UnitTests/Coding/DTODecodableTests.swift diff --git a/NodeKitTests/UnitTests/Coding/DictionaryDTOConvertibleTests.swift b/NodeKit/NodeKitTests/UnitTests/Coding/DictionaryDTOConvertibleTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Coding/DictionaryDTOConvertibleTests.swift rename to NodeKit/NodeKitTests/UnitTests/Coding/DictionaryDTOConvertibleTests.swift diff --git a/NodeKitTests/UnitTests/Coding/EncodingTests.swift b/NodeKit/NodeKitTests/UnitTests/Coding/EncodingTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Coding/EncodingTests.swift rename to NodeKit/NodeKitTests/UnitTests/Coding/EncodingTests.swift diff --git a/NodeKitTests/UnitTests/Coding/RawDecodableTests.swift b/NodeKit/NodeKitTests/UnitTests/Coding/RawDecodableTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Coding/RawDecodableTests.swift rename to NodeKit/NodeKitTests/UnitTests/Coding/RawDecodableTests.swift diff --git a/NodeKitTests/UnitTests/Core/AsyncNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Core/AsyncNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Core/AsyncNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Core/AsyncNodeTests.swift diff --git a/NodeKitTests/UnitTests/Core/AsyncStreamNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Core/AsyncStreamNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Core/AsyncStreamNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Core/AsyncStreamNodeTests.swift diff --git a/NodeKitTests/UnitTests/Core/Combine/AsyncCombineNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Core/Combine/AsyncCombineNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Core/Combine/AsyncCombineNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Core/Combine/AsyncCombineNodeTests.swift diff --git a/NodeKitTests/UnitTests/Core/Combine/AsyncStreamCombineNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Core/Combine/AsyncStreamCombineNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Core/Combine/AsyncStreamCombineNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Core/Combine/AsyncStreamCombineNodeTests.swift diff --git a/NodeKitTests/UnitTests/Core/Combine/CombineNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Core/Combine/CombineNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Core/Combine/CombineNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Core/Combine/CombineNodeTests.swift diff --git a/NodeKitTests/UnitTests/Core/Combine/CombineStreamNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Core/Combine/CombineStreamNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Core/Combine/CombineStreamNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Core/Combine/CombineStreamNodeTests.swift diff --git a/NodeKitTests/UnitTests/Core/NodeResultTests.swift b/NodeKit/NodeKitTests/UnitTests/Core/NodeResultTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Core/NodeResultTests.swift rename to NodeKit/NodeKitTests/UnitTests/Core/NodeResultTests.swift diff --git a/NodeKitTests/UnitTests/Logging/LogableTests.swift b/NodeKit/NodeKitTests/UnitTests/Logging/LogableTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Logging/LogableTests.swift rename to NodeKit/NodeKitTests/UnitTests/Logging/LogableTests.swift diff --git a/NodeKitTests/UnitTests/Logging/LoggingContextTests.swift b/NodeKit/NodeKitTests/UnitTests/Logging/LoggingContextTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Logging/LoggingContextTests.swift rename to NodeKit/NodeKitTests/UnitTests/Logging/LoggingContextTests.swift diff --git a/NodeKitTests/UnitTests/Network/AlamofireMultipartFormDataFactoryTests.swift b/NodeKit/NodeKitTests/UnitTests/Network/AlamofireMultipartFormDataFactoryTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Network/AlamofireMultipartFormDataFactoryTests.swift rename to NodeKit/NodeKitTests/UnitTests/Network/AlamofireMultipartFormDataFactoryTests.swift diff --git a/NodeKitTests/UnitTests/Network/MultipartModelTests.swift b/NodeKit/NodeKitTests/UnitTests/Network/MultipartModelTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Network/MultipartModelTests.swift rename to NodeKit/NodeKitTests/UnitTests/Network/MultipartModelTests.swift diff --git a/NodeKitTests/UnitTests/Network/ServerRequestsManagerTests.swift b/NodeKit/NodeKitTests/UnitTests/Network/ServerRequestsManagerTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Network/ServerRequestsManagerTests.swift rename to NodeKit/NodeKitTests/UnitTests/Network/ServerRequestsManagerTests.swift diff --git a/NodeKitTests/UnitTests/Network/URLRoutingTests.swift b/NodeKit/NodeKitTests/UnitTests/Network/URLRoutingTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Network/URLRoutingTests.swift rename to NodeKit/NodeKitTests/UnitTests/Network/URLRoutingTests.swift diff --git a/NodeKitTests/UnitTests/Network/URLSessionDataTaskActorTests.swift b/NodeKit/NodeKitTests/UnitTests/Network/URLSessionDataTaskActorTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Network/URLSessionDataTaskActorTests.swift rename to NodeKit/NodeKitTests/UnitTests/Network/URLSessionDataTaskActorTests.swift diff --git a/NodeKitTests/UnitTests/Network/UrlChainConfigModelTests.swift b/NodeKit/NodeKitTests/UnitTests/Network/UrlChainConfigModelTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Network/UrlChainConfigModelTests.swift rename to NodeKit/NodeKitTests/UnitTests/Network/UrlChainConfigModelTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/AbortingTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/AbortingTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/AbortingTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/AbortingTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/AccessSafeNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/AccessSafeNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/AccessSafeNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/AccessSafeNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/CacheReaderNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/CacheReaderNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/CacheReaderNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/CacheReaderNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/DTOEncoderNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/DTOEncoderNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/DTOEncoderNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/DTOEncoderNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/DTOMapperNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/DataLoadingResponseProcessorTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/DataLoadingResponseProcessorTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/DataLoadingResponseProcessorTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/DataLoadingResponseProcessorTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/EntryinputDtoOutputNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/EntryinputDtoOutputNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/EntryinputDtoOutputNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/EntryinputDtoOutputNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/HeaderInjectorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/HeaderInjectorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/HeaderInjectorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/HeaderInjectorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/IfConnectionFailedFromCacheNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/IfConnectionFailedFromCacheNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/IfConnectionFailedFromCacheNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/IfConnectionFailedFromCacheNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/LoadIndicatableNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/LoadIndicatableNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/LoadIndicatableNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/LoadIndicatableNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/LoggerNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/LoggerStreamNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/LoggerStreamNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/LoggerStreamNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/LoggerStreamNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/MetadataConnectorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/MetadataConnectorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/MetadataConnectorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/MetadataConnectorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/MockerProxyConfigNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/MockerProxyConfigNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/MockerProxyConfigNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/MockerProxyConfigNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/ModelInputNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/ModelInputNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/ModelInputNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/ModelInputNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/MultipartRequestCreatorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/MultipartRequestCreatorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/MultipartRequestCreatorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/MultipartRequestCreatorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/MultipartUrlRequestTrasformatorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/MultipartUrlRequestTrasformatorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/MultipartUrlRequestTrasformatorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/MultipartUrlRequestTrasformatorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/RawEncoderNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/RawEncoderNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/RawEncoderNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/RawEncoderNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/RequestCreatorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/RequestCreatorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/RequestCreatorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/RequestCreatorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/RequestEncoderNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/RequestEncoderNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/RequestEncoderNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/RequestEncoderNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/RequestRouterNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/RequestRouterNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/RequestRouterNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/RequestRouterNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/RequestSenderNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/RequestSenderNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/RequestSenderNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/RequestSenderNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/ResponseDataParserNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/ResponseDataParserNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/ResponseDataParserNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/ResponseDataParserNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/ResponseDataPreprocessorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/ResponseDataPreprocessorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/ResponseDataPreprocessorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/ResponseDataPreprocessorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/ResponseHttpErrorProcessorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/ResponseHttpErrorProcessorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/ResponseHttpErrorProcessorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/ResponseHttpErrorProcessorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/ResponseProcessorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/ResponseProcessorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/ResponseProcessorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/ResponseProcessorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/TechnicaErrorMapperNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/TechnicaErrorMapperNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/TechnicaErrorMapperNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/TechnicaErrorMapperNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/TokenRefresherNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/TokenRefresherNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/TokenRefresherNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/TokenRefresherNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/URLQueryInjectorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/URLQueryInjectorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/URLQueryInjectorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/URLQueryInjectorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/UrlCacheWriterNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/UrlCacheWriterNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/UrlCacheWriterNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/UrlCacheWriterNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/UrlETagReaderNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/UrlETagReaderNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/UrlETagReaderNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/UrlETagReaderNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/UrlETagSaverNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/UrlETagSaverNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/UrlETagSaverNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/UrlETagSaverNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/UrlETagUrlCacheTriggerNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/UrlETagUrlCacheTriggerNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/UrlETagUrlCacheTriggerNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/UrlETagUrlCacheTriggerNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/UrlRequestTrasformatorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/UrlRequestTrasformatorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/UrlRequestTrasformatorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/UrlRequestTrasformatorNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/VoidIONodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/VoidInputNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/VoidInputNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/VoidInputNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/VoidInputNodeTests.swift diff --git a/NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/Nodes/VoidOutputNodeTests.swift diff --git a/NodeKitTests/UnitTests/RequestBuildingLayer/RequestCreatorNodeTests.swift b/NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/RequestCreatorNodeTests.swift similarity index 100% rename from NodeKitTests/UnitTests/RequestBuildingLayer/RequestCreatorNodeTests.swift rename to NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/RequestCreatorNodeTests.swift diff --git a/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryArrayKeyEncodingBracketsStartegyTests.swift b/NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryArrayKeyEncodingBracketsStartegyTests.swift similarity index 100% rename from NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryArrayKeyEncodingBracketsStartegyTests.swift rename to NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryArrayKeyEncodingBracketsStartegyTests.swift diff --git a/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryBoolEncodingDefaultStartegyTests.swift b/NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryBoolEncodingDefaultStartegyTests.swift similarity index 100% rename from NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryBoolEncodingDefaultStartegyTests.swift rename to NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryBoolEncodingDefaultStartegyTests.swift diff --git a/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift b/NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift similarity index 100% rename from NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift rename to NodeKit/NodeKitTests/UnitTests/RequestBuildingLayer/URLQueryDictionaryKeyEncodingDefaultStrategyTests.swift diff --git a/NodeKitTests/UnitTests/TokenRefresher/TokenRefresherActorTests.swift b/NodeKit/NodeKitTests/UnitTests/TokenRefresher/TokenRefresherActorTests.swift similarity index 100% rename from NodeKitTests/UnitTests/TokenRefresher/TokenRefresherActorTests.swift rename to NodeKit/NodeKitTests/UnitTests/TokenRefresher/TokenRefresherActorTests.swift diff --git a/NodeKit/NodeKitThirdParty/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/NodeKit/NodeKitThirdParty/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/NodeKit/NodeKitThirdParty/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/NodeKitThirdParty/Package.swift b/NodeKit/NodeKitThirdParty/Package.swift similarity index 100% rename from NodeKitThirdParty/Package.swift rename to NodeKit/NodeKitThirdParty/Package.swift diff --git a/NodeKitThirdParty/Source/Alamofire/AFError.swift b/NodeKit/NodeKitThirdParty/Source/Alamofire/AFError.swift similarity index 100% rename from NodeKitThirdParty/Source/Alamofire/AFError.swift rename to NodeKit/NodeKitThirdParty/Source/Alamofire/AFError.swift diff --git a/NodeKitThirdParty/Source/Alamofire/HTTPHeaders.swift b/NodeKit/NodeKitThirdParty/Source/Alamofire/HTTPHeaders.swift similarity index 100% rename from NodeKitThirdParty/Source/Alamofire/HTTPHeaders.swift rename to NodeKit/NodeKitThirdParty/Source/Alamofire/HTTPHeaders.swift diff --git a/NodeKitThirdParty/Source/Alamofire/MultipartFormData.swift b/NodeKit/NodeKitThirdParty/Source/Alamofire/MultipartFormData.swift similarity index 100% rename from NodeKitThirdParty/Source/Alamofire/MultipartFormData.swift rename to NodeKit/NodeKitThirdParty/Source/Alamofire/MultipartFormData.swift diff --git a/NodeKitThirdParty/Source/Alamofire/MultipartFormDataProtocol.swift b/NodeKit/NodeKitThirdParty/Source/Alamofire/MultipartFormDataProtocol.swift similarity index 100% rename from NodeKitThirdParty/Source/Alamofire/MultipartFormDataProtocol.swift rename to NodeKit/NodeKitThirdParty/Source/Alamofire/MultipartFormDataProtocol.swift diff --git a/Package.swift b/NodeKit/Package.swift similarity index 100% rename from Package.swift rename to NodeKit/Package.swift diff --git a/NodeKit/Utils/AsyncIterator/OffsetAsyncPager.swift b/NodeKit/Utils/AsyncIterator/OffsetAsyncPager.swift deleted file mode 100644 index 0b65968e..00000000 --- a/NodeKit/Utils/AsyncIterator/OffsetAsyncPager.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// OffsetAsyncPager.swift -// NodeKit -// - -/// Предоставляет возможность делать пагинацию на оффсетах -public actor OffsetAsyncPager: AsyncIterator, StateStorable { - - /// Возвращаемый тип и количество элементов для увеличения смещения - typealias PagingData = (data: Value, len: Int) - /// Специальный объект, который выполняет пагинацию, например, через запросы на сервер на основе index и pageSize - typealias DataProvider = (_ index: Int, _ pageSize: Int) -> NodeResult - - private struct PagerState { - var index: Int - var pageSize: Int - } - - private let dataProvider: DataProvider - private var currentState: PagerState - private var statesStore = [PagerState]() - - init(dataProvider: @escaping DataProvider, pageSize: Int) { - self.dataProvider = dataProvider - self.currentState = PagerState(index: 0, pageSize: pageSize) - } - - public func next() -> Result<(data: Value, end: Bool), Error> { - return dataProvider(currentState.index, currentState.pageSize) - .flatMap { (data, len) in - currentState.index += len - return .success((data, len == 0 || len < currentState.pageSize)) - } - } - - public func renew() { - currentState.index = 0 - } - - public func saveState() { - statesStore.append(currentState) - } - - public func clearStates() { - statesStore.removeAll() - } - - public func restoreState() { - currentState = statesStore.last != nil ? statesStore.removeLast() : currentState - } -} diff --git a/NodeKitTests/UnitTests/AsyncIterator/OffsetAsyncPagerTests.swift b/NodeKitTests/UnitTests/AsyncIterator/OffsetAsyncPagerTests.swift deleted file mode 100644 index c9989392..00000000 --- a/NodeKitTests/UnitTests/AsyncIterator/OffsetAsyncPagerTests.swift +++ /dev/null @@ -1,245 +0,0 @@ -// -// OffsetAsyncPagerTests.swift -// NodeKitTests -// -// Created by Andrei Frolov on 04.04.24. -// Copyright © 2024 Surf. All rights reserved. -// - -@testable import NodeKit -@testable import NodeKitMock - -import XCTest - -final class OffsetAsyncPagerTests: XCTestCase { - - // MARK: - Tests - - func testNext_withArray_thenArrayWithNoEndReceived() async throws { - // given - - let expectedArr = ["1", "2", "3", "4", "5"] - - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - return .success((expectedArr, expectedArr.count)) - }, pageSize: 5) - - // when - - let result = await iterator.next() - - // then - - let unwrappedResult = try XCTUnwrap(result.value) - - XCTAssertEqual(unwrappedResult.data, expectedArr) - XCTAssertFalse(unwrappedResult.end) - } - - func testNext_withEmptySecondArray_thenEndReceivedOnlyLastTime() async throws { - // given - - let expectedArr = ["1", "2", "3", "4", "5"] - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - if index >= 5 { - return .success(([], 0)) - } - - return .success((expectedArr, expectedArr.count)) - }, pageSize: 5) - - var firstResult: Result<(data: [String], end: Bool), Error>? - var secondResult: Result<(data: [String], end: Bool), Error>? - - // when - - firstResult = await iterator.next() - secondResult = await iterator.next() - - // then - - let unwrapedFirstValue = try XCTUnwrap(firstResult?.value) - let unwrappedSecondValue = try XCTUnwrap(secondResult?.value) - - XCTAssertEqual(unwrapedFirstValue.data, expectedArr) - XCTAssertFalse(unwrapedFirstValue.end) - XCTAssertEqual(unwrappedSecondValue.data, []) - XCTAssertTrue(unwrappedSecondValue.end) - } - - func testNext_withNonEmptySecondArray_thenEndReceivedOnlyLastTime() async throws { - // given - - let expectedFirstArr = ["1", "2", "3", "4", "5"] - let expectedSecondArr = ["1"] - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - if index >= 5 { - return .success((expectedSecondArr, expectedSecondArr.count)) - } - - return .success((expectedFirstArr, expectedFirstArr.count)) - }, pageSize: 5) - - - var firstResult: Result<(data: [String], end: Bool), Error>? - var secondResult: Result<(data: [String], end: Bool), Error>? - - // when - - firstResult = await iterator.next() - secondResult = await iterator.next() - - // then - - let unwrapedFirstValue = try XCTUnwrap(firstResult?.value) - let unwrappedSecondValue = try XCTUnwrap(secondResult?.value) - - XCTAssertEqual(unwrapedFirstValue.data, expectedFirstArr) - XCTAssertFalse(unwrapedFirstValue.end) - XCTAssertEqual(unwrappedSecondValue.data, expectedSecondArr) - XCTAssertTrue(unwrappedSecondValue.end) - } - - func testNext_withError_thenErrorReceived() async throws { - // given - - let expectedError = MockError.firstError - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - return .failure(expectedError) - }, pageSize: 5) - - - // when - - let result = await iterator.next() - - // then - - let error = try XCTUnwrap(result.error as? MockError) - - XCTAssertEqual(error, expectedError) - } - - func testRenew_thenZeroIndexReceived() async throws { - // given - - let expectedArr = ["1", "2", "3", "4", "5"] - var indexes: [Int] = [] - - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - indexes.append(index) - return .success((expectedArr, expectedArr.count)) - }, pageSize: 5) - - // when - - _ = await iterator.next() - _ = await iterator.next() - - await iterator.renew() - - _ = await iterator.next() - - // then - - XCTAssertEqual(indexes, [0, 5, 0]) - } - - func testSaveState_thenIteratorStartFromSavedState() async throws { - // given - - let expectedArr = ["1", "2", "3", "4", "5"] - var indexes: [Int] = [] - var offsets: [Int] = [] - - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - indexes.append(index) - offsets.append(offset) - return .success((expectedArr, expectedArr.count)) - }, pageSize: 5) - - // when - - await iterator.saveState() - - _ = await iterator.next() - _ = await iterator.next() - - await iterator.restoreState() - - let _ = await iterator.next() - - // then - - XCTAssertEqual(indexes, [0, 5, 0]) - XCTAssertEqual(offsets, [5, 5, 5]) - } - - func testSaveState_whenSaveTwoStates_thenIteratorStartFromSavedState() async throws { - // given - - let expectedArr = ["1", "2", "3", "4", "5"] - var indexes: [Int] = [] - var offsets: [Int] = [] - - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - indexes.append(index) - offsets.append(offset) - return .success((expectedArr, expectedArr.count)) - }, pageSize: 5) - - // when - - await iterator.saveState() - - _ = await iterator.next() - - await iterator.saveState() - - _ = await iterator.next() - - await iterator.restoreState() - - let _ = await iterator.next() - - await iterator.restoreState() - - let _ = await iterator.next() - - // then - - XCTAssertEqual(indexes, [0, 5, 5, 0]) - XCTAssertEqual(offsets, [5, 5, 5, 5]) - } - - func testClearState_thenSavedStateCleared() async throws { - // given - - let expectedArr = ["1", "2", "3", "4", "5"] - var indexes: [Int] = [] - var offsets: [Int] = [] - - let iterator = OffsetAsyncPager<[String]>(dataProvider: { index, offset in - indexes.append(index) - offsets.append(offset) - return .success((expectedArr, expectedArr.count)) - }, pageSize: 5) - - // when - - await iterator.saveState() - - _ = await iterator.next() - _ = await iterator.next() - - await iterator.clearStates() - await iterator.restoreState() - - _ = await iterator.next() - - // then - - XCTAssertEqual(indexes, [0, 5, 10]) - XCTAssertEqual(offsets, [5, 5, 5]) - } -} diff --git a/SimpleExample.playground/Contents.swift b/SimpleExample.playground/Contents.swift deleted file mode 100644 index 703a92b0..00000000 --- a/SimpleExample.playground/Contents.swift +++ /dev/null @@ -1,178 +0,0 @@ -import UIKit -import NodeKit - -// MARK: - Инфраструктура - -enum CustomError: Error { - case badUrl -} - -enum Endpoint { - case isHttp2 -} - -extension URL { - static func from(_ string: String) throws -> URL { - guard let url = URL(string: string) else { - throw CustomError.badUrl - } - return url - } -} - -extension Endpoint: UrlRouteProvider { - func url() throws -> URL { - switch self { - case .isHttp2: - return try .from("https://http2.pro/api/v1") - } - } -} - -/// Пример запроса без параметров. - -/// ---------------- Модели для запроса - -/// DTOConvertible (верхний слой) -struct Http2CheckResult { - let status: Http2Status - let `protocol`: String - let push: Http2Status - let userAgent: String -} - -extension Http2CheckResult: DTODecodable { - - typealias DTO = Http2CheckResultEntry - - static func from(dto model: Http2CheckResultEntry) throws -> Http2CheckResult { - return .init(status: model.http2, - protocol: model.protocol, - push: model.push, - userAgent: model.user_agent) - } -} - -enum Http2Status: Int, Codable { - case used = 2 - case notUsed = 1 -} - -/// RawMappable (нижний слой) - -struct Http2CheckResultEntry: Codable { - let http2: Http2Status - let `protocol`: String - let push: Http2Status - let user_agent: String -} - -extension Http2CheckResultEntry: RawDecodable { - typealias Raw = Json -} - -// ------- Сервис - -func checkHttp2() -> Observer { - return UrlChainsBuilder() - .route(.post, Endpoint.isHttp2) - .log(exclude: ["ResponseDataParserNode"]) - .build() - .process() -} - -// ---------- Presenter - -print("checkHttp2") - -let cnt = checkHttp2().onCompleted { result in - print(result.status) - print("Protocol: \(result.protocol)") - print("Server-push: \(result.push)") - print(result.userAgent) -}.onError { error in - print(error) -} - - -// POST lastsprint.dev:8822/pfood/auth -// -//{"profile":{"balance":100,"birthday":"1990-09-02","email":"test@test2.ru","gender":1,"has_orders":false,"has_saved_cards":false,"id":"12656941231","name":"Иван Иванов","phone":"79001234567"},"tokens":{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNTE2MjM5MDIyfQ.AgNLCIRwkhE9zEvARcUz3dhxFH6MvrZVXrWEfm7X9Xs","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNTE2MjM5MDIyfQ.AgNLCIRwkhE9zEvARcUz3dhxFH6MvrZVXrWEfm7X9Xs"}} - -// Entity - -struct AuthEntity: DTODecodable { - let profile: ProfileEntity - let tokens: TokensEntity - - static func from(dto: AuthEntry) throws -> AuthEntity { - return try .init(profile: .from(dto: dto.profile), tokens: .from(dto: dto.tokens)) - } -} - -struct AuthEntry: Codable, RawMappable { - let profile: ProfileEntry - let tokens: TokensEntry - - typealias Raw = Json -} - -struct ProfileEntity: DTODecodable { - - typealias DTO = ProfileEntry - - let balance: Double - let birthday: String - let email: String - - static func from(dto: ProfileEntry) throws -> ProfileEntity { - return .init(balance: dto.balance, birthday: dto.birthday, email: dto.email) - } -} - -struct TokensEntity: DTODecodable { - let accessToken: String - let refreshToken: String - - static func from(dto: TokensEntry) throws -> TokensEntity { - return .init(accessToken: dto.access_token, refreshToken: dto.refresh_token) - } -} - -// Entry - -struct ProfileEntry: Codable, RawMappable { - - typealias Raw = Json - - let balance: Double - let birthday: String - let email: String -} - -struct TokensEntry: Codable, RawMappable { - - typealias Raw = Json - - let access_token: String - let refresh_token: String -} - -enum AuthRoute: UrlRouteProvider { - case auth - - func url() throws -> URL { - return URL(string: "http://lastsprint.dev:8822/pfood/auth")! - } -} - -func auth() -> Observer { - return UrlChainsBuilder() - .route(.post, AuthRoute.auth) - .build() - .process() -} - -auth().onCompleted { model in - print(model.profile) -} diff --git a/SimpleExample.playground/contents.xcplayground b/SimpleExample.playground/contents.xcplayground deleted file mode 100644 index 84a22c92..00000000 --- a/SimpleExample.playground/contents.xcplayground +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TestServer/TestServer b/TestServer/TestServer deleted file mode 100755 index f6d55c88..00000000 Binary files a/TestServer/TestServer and /dev/null differ diff --git a/TestServer/deploy.sh b/TestServer/deploy.sh deleted file mode 100644 index 2ed87828..00000000 --- a/TestServer/deploy.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -cd /home/guest/stage/CoreNetKit/TestServer -echo 'CD COMPLETE' -go build . -echo 'BUILD COMPLETE' -./TestServer -echo 'RUN' -echo '\n' \ No newline at end of file diff --git a/TestServer/go.mod b/TestServer/go.mod deleted file mode 100644 index fd581073..00000000 --- a/TestServer/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module github.com/surfstudio/NodeKit/TestServer - -go 1.13 - -require ( - github.com/gorilla/mux v1.7.3 - gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 -) diff --git a/TestServer/go.sum b/TestServer/go.sum deleted file mode 100644 index 0c58dfe8..00000000 --- a/TestServer/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= diff --git a/TestServer/main.go b/TestServer/main.go deleted file mode 100644 index 908cf23f..00000000 --- a/TestServer/main.go +++ /dev/null @@ -1,290 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "log" - "net/http" - "os" - - "io/ioutil" - - "strconv" - - "github.com/gorilla/mux" - - "gopkg.in/mgo.v2/bson" -) - -// User stub model -type User struct { - ID string `json:"id,omitempty"` - Firstname string `json:"firstName,omitempty"` - Lastname string `json:"lastName,omitempty"` -} - -func main() { - router := mux.NewRouter() - addHTTPListners(router) - - var server = http.Server{Addr: ":8118", Handler: router} - - router.HandleFunc("/nkt/shutdown", func(w http.ResponseWriter, r *http.Request) { - server.Shutdown(context.Background()) - }) - - if err := server.ListenAndServe(); err != nil { - log.Println(err) - os.Exit(0) - } - - log.Println("Server starts successfully on port 8118") -} - -func addHTTPListners(router *mux.Router) { - router.HandleFunc("/nkt/users/{id}", GetUser).Methods("GET") - router.HandleFunc("/nkt/users", GetUsers).Methods("GET") - router.HandleFunc("/nkt/items", GetItemList).Methods("GET") - router.HandleFunc("/nkt/userAmptyArr", GetEmptyUserArr).Methods("GET") - router.HandleFunc("/nkt/Get402UserArr", Get402UserArr).Methods("GET") - - router.HandleFunc("/nkt/users", AddNewUser).Methods("POST") - router.HandleFunc("/nkt/authWithFormUrl", AuthWithFormURL).Methods("POST") - router.HandleFunc("/nkt/multipartPing", MultipartPing).Methods("POST") - router.HandleFunc("/nkt/multipartCorrect", MultipartCorrect).Methods("POST") - router.HandleFunc("/nkt/multipartFile", multipartFile).Methods("POST") - - router.HandleFunc("/nkt/bson", bsonGet).Methods("GET") - router.HandleFunc("/nkt/bson", bsonPost).Methods("POST") -} - -// GetUser description -// 500 error with message "Something went wrong" id = 0 -// 403 error id = 1 -// 400 error id = 2 -// 200 success id = any other -// Returns user with recived Id -func GetUser(w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - switch params["id"] { - case "0": - http.Error(w, "Something went wrong", 500) - case "1": - http.Error(w, "", 403) - case "2": - http.Error(w, "", 400) - default: - json.NewEncoder(w).Encode(User{ID: params["id"], Firstname: "John", Lastname: "Jackson"}) - } - w.Header().Set("Content-Type", "application/json") -} - -// GetUsers return 4 users -func GetUsers(w http.ResponseWriter, r *http.Request) { - - var users []User - - stackArr := r.URL.Query()["stack"] - sortArr := r.URL.Query()["sort"] - - if len(stackArr) == 1 && len(sortArr) == 1 && stackArr[0] == "left" && sortArr[0] == "false" { - users = append(users, User{ID: "id0", Lastname: "Bender0", Firstname: "Rodrigez0"}) - users = append(users, User{ID: "id1", Lastname: "Bender1", Firstname: "Rodrigez1"}) - users = append(users, User{ID: "id2", Lastname: "Bender2", Firstname: "Rodrigez2"}) - users = append(users, User{ID: "id3", Lastname: "Bender3", Firstname: "Rodrigez3"}) - } else { - users = append(users, User{ID: "id0", Lastname: "Fry0", Firstname: "Philip0"}) - users = append(users, User{ID: "id1", Lastname: "Fry1", Firstname: "Philip1"}) - users = append(users, User{ID: "id2", Lastname: "Fry2", Firstname: "Philip2"}) - users = append(users, User{ID: "id3", Lastname: "Fry3", Firstname: "Philip3"}) - } - - json.NewEncoder(w).Encode(users) - w.Header().Set("Content-Type", "application/json") -} - -// GetEmptyUserArr just return an empty array in response body -func GetEmptyUserArr(w http.ResponseWriter, r *http.Request) { - - var users []User - - json.NewEncoder(w).Encode(users) - w.Header().Set("Content-Type", "application/json") -} - -// Get402UserArr just return 204 response code that means "no response" -func Get402UserArr(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(204) - return -} - -// GetItemList return item with offset paging -// If we cant convert request count field to int - method throws http error 400 -// If count == 0 or 5 return empty list (it means that paging ends) -// If count == 3 return 500 error -func GetItemList(w http.ResponseWriter, r *http.Request) { - params, ok := r.URL.Query()["count"] - - if !ok || len(params[0]) < 1 { - http.Error(w, "Bad count", 400) - return - } - - count, error := strconv.Atoi(params[0]) - - if error != nil { - http.Error(w, "Bad count", 402) - return - } - - if count == 3 { - http.Error(w, "Something went wrong", 500) - return - } - - if count == 0 || count == -5 { - w.WriteHeader(204) - return - } - - var users []User - - for index := 0; index < count; index++ { - stringIndex := strconv.Itoa(index) - users = append(users, User{ID: stringIndex, Lastname: stringIndex, Firstname: stringIndex}) - } - json.NewEncoder(w).Encode(users) -} - -// AddNewUser awaits user with specific id -// id == 1 response 409 with message "Already exist" -// id == Any other response 201 -// body not exist = response 400 -func AddNewUser(w http.ResponseWriter, r *http.Request) { - var user User - if json.NewDecoder(r.Body).Decode(&user) != nil { - w.WriteHeader(400) - return - } - - switch user.ID { - case "409": - http.Error(w, "Already exist", 409) - default: - w.WriteHeader(201) - } -} - -// AuthWithFormURL provides www-form-url-encoded endpoint that await form like: -// secret = "secret" -// type = "type" -// In success case return json: -// { "accessToken": "token", "refreshToken": "token" } -// In failure case return 402 code -func AuthWithFormURL(w http.ResponseWriter, r *http.Request) { - log.Println(r) - r.ParseForm() - - var secret = r.FormValue("secret") - var typeVal = r.FormValue("type") - - log.Println(r) - log.Println(r.Form) - - if secret == "secret" && typeVal == "type" { - json.NewEncoder(w).Encode(map[string]string{"accessToken": "token", "refreshToken": "token"}) - } - - w.WriteHeader(http.StatusBadRequest) -} - -// MultipartPing providig form/multipart endpoint -func MultipartPing(w http.ResponseWriter, r *http.Request) { - r.ParseMultipartForm(32 << 20) - // fmt.Println(r.FormFile("file1")) - // fmt.Println(r.FormFile("file1")) - // fmt.Println(r.Form) - // fmt.Println(r.MultipartForm) - json.NewEncoder(w).Encode(map[string]bool{"success": true}) -} - -// MultipartCorrect check value is correct -func MultipartCorrect(w http.ResponseWriter, r *http.Request) { - r.ParseMultipartForm(32 << 20) - - success := true - - if r.FormValue("word1") != "Test" { - success = false - } - - if r.FormValue("word2") != "Success" { - success = false - } - - json.NewEncoder(w).Encode(map[string]bool{"success": success}) -} - -func multipartFile(w http.ResponseWriter, r *http.Request) { - r.ParseMultipartForm(32 << 20) - - success := true - - _, header, err := r.FormFile("file") - - if err != nil { - success = false - } - - if header.Filename != "LICENSE.txt" { - success = false - } - - if header.Header.Get("Content-Type") != "text/plain" { - success = false - } - - json.NewEncoder(w).Encode(map[string]bool{"success": success}) -} - -func bsonGet(w http.ResponseWriter, r *http.Request) { - usr := User{ID: "123", Lastname: "Freeze", Firstname: "John"} - - data, err := bson.Marshal(&usr) - - if err != nil { - http.Error(w, "Cant map", 500) - return - } - - w.Write(data) - - w.Header().Set("Content-Type", "application/bson") -} - -func bsonPost(w http.ResponseWriter, r *http.Request) { - var user User - - data, err := ioutil.ReadAll(r.Body) - - if err != nil { - w.WriteHeader(400) - return - } - - err = bson.Unmarshal(data, &user) - - if err != nil { - w.WriteHeader(500) - return - } - - switch user.ID { - case "409": - http.Error(w, "Already exist", 409) - default: - w.WriteHeader(201) - } - - w.Header().Set("Content-Type", "application/bson") -} \ No newline at end of file diff --git a/TestServer/pkg/mod/cache/lock b/TestServer/pkg/mod/cache/lock deleted file mode 100644 index e69de29b..00000000 diff --git a/ci/codecov_report.sh b/ci/codecov_report.sh deleted file mode 100644 index d96f2a36..00000000 --- a/ci/codecov_report.sh +++ /dev/null @@ -1,2 +0,0 @@ -set -o pipefail -bash <(curl -s https://codecov.io/bash) -t b62fa427-b5d3-4318-885d-e7783e1d527c \ No newline at end of file diff --git a/ci/deploy_server.sh b/ci/deploy_server.sh deleted file mode 100644 index 23a58ea7..00000000 --- a/ci/deploy_server.sh +++ /dev/null @@ -1,5 +0,0 @@ -scp -i ssh/id_rsa -P 22334 -r TestServer guest@lastsprint.dev:/home/guest/stage/CoreNetKit -ssh -i ssh/id_rsa \ --p 22334 \ -guest@lastsprint.dev \ -stage/CoreNetKit/TestServer/deploy.sh & \ No newline at end of file diff --git a/ci/docks/deploy_docs.sh b/ci/docks/deploy_docs.sh deleted file mode 100755 index a2ce9cc6..00000000 --- a/ci/docks/deploy_docs.sh +++ /dev/null @@ -1,2 +0,0 @@ -sh ./dock_gen.sh -scp -i ssh/id_rsa -P 22334 -r Docs guest@lastsprint.dev:/var/www/html/CoreNetKit \ No newline at end of file diff --git a/ci/docks/dock_gen.sh b/ci/docks/dock_gen.sh deleted file mode 100755 index 92102f60..00000000 --- a/ci/docks/dock_gen.sh +++ /dev/null @@ -1,8 +0,0 @@ -jazzy \ - --clean \ - --author Alexander Kravchenkov \ - --author_url https://lastsprint.dev \ - --github_url https://github.com/surfstudio/NodeKit \ - --xcodebuild-arguments -scheme,NodeKit \ - --module ../../../NodeKit \ - --output ../../../docs/swift_output \ \ No newline at end of file diff --git a/ci/prepare_srv_deploy.sh b/ci/prepare_srv_deploy.sh deleted file mode 100644 index bc0acd51..00000000 --- a/ci/prepare_srv_deploy.sh +++ /dev/null @@ -1,4 +0,0 @@ -eval "$(ssh-agent -s)" -chmod 600 ssh/id_rsa -echo "Host lastsprint.dev\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config -ssh-add ssh/id_rsa \ No newline at end of file diff --git a/ci/prepare_tests.sh b/ci/prepare_tests.sh deleted file mode 100644 index 58899bf4..00000000 --- a/ci/prepare_tests.sh +++ /dev/null @@ -1,2 +0,0 @@ -sed -i '' 's|http://localhost:8118|https://lastsprint.dev|' IntegrationTests/Infrastructure/Infrastructure.swift -cat IntegrationTests/Infrastructure/Infrastructure.swift diff --git a/ci/run_tests.sh b/ci/run_tests.sh deleted file mode 100644 index 324978a5..00000000 --- a/ci/run_tests.sh +++ /dev/null @@ -1,7 +0,0 @@ -xcodebuild test \ --workspace NodeKit.xcworkspace \ --scheme IntegrationTests \ --configuration "Debug" \ --sdk iphonesimulator \ --enableCodeCoverage YES \ --destination 'platform=iOS Simulator,name=iPhone 5s,OS=12.2' | xcpretty -c \ No newline at end of file diff --git a/ssh/id_rsa.enc b/ssh/id_rsa.enc deleted file mode 100644 index 9a28a495..00000000 Binary files a/ssh/id_rsa.enc and /dev/null differ diff --git a/ssh/id_rsa.pub b/ssh/id_rsa.pub deleted file mode 100644 index e37126bf..00000000 --- a/ssh/id_rsa.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNJ1YQkJcHRLGKDaDrv6kW8ADBomJyUEvUckefA6lRG/Le3cBT9qgUKceFT8IV+k6d5KP+3K9I3K2MyreTvTybImqBXvApN8BbezQxDob36f0wd6b0Iz1ggUDXtreBxiT7GQSaNz8j27vwFTZIcqkRdL6qEDZg/5bycHVESeL2KM/4FFVe8OFsM7jldQ8Ls09FcQmNedxekPXXHgxGARXnn8htaywTSQWUjgLg+BxTg6DiE97Zmx6atOqehG3ZgrgNpl1JZYT86ATbBJGZW8ZeKFL6FtQQsUy2NPm2QyGSuf/YMaaxuOg9fPHdf1YaurLQ83/nj+nRVweh53l1iFTZ aleksandrkravcenkov@MBP-Aleksandr