diff --git a/Example/MobileContentApi.xcodeproj/project.pbxproj b/Example/MobileContentApi.xcodeproj/project.pbxproj deleted file mode 100644 index 282c21f..0000000 --- a/Example/MobileContentApi.xcodeproj/project.pbxproj +++ /dev/null @@ -1,753 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 56; - objects = { - -/* Begin PBXBuildFile section */ - 450E55AD2C10E6D7000B96D0 /* ContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450E55AC2C10E6D7000B96D0 /* ContentViewModel.swift */; }; - 45208B2F2C626DF3000295F6 /* LanguageResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45208B2D2C626DF3000295F6 /* LanguageResource.swift */; }; - 45208B322C626E0D000295F6 /* GetLanguagesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45208B312C626E0D000295F6 /* GetLanguagesEndpoint.swift */; }; - 45208B342C626E14000295F6 /* GetLanguageEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45208B332C626E14000295F6 /* GetLanguageEndpoint.swift */; }; - 45208B372C626EE7000295F6 /* LanguageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45208B362C626EE7000295F6 /* LanguageModel.swift */; }; - 4538ADF42C10981200A7CFEC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4538ADF32C10981200A7CFEC /* Assets.xcassets */; }; - 4538ADF72C10981200A7CFEC /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4538ADF62C10981200A7CFEC /* Preview Assets.xcassets */; }; - 4538AE012C10981200A7CFEC /* MobileContentApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4538AE002C10981200A7CFEC /* MobileContentApiTests.swift */; }; - 4538AE0B2C10981200A7CFEC /* MobileContentApiUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4538AE0A2C10981200A7CFEC /* MobileContentApiUITests.swift */; }; - 4538AE0D2C10981200A7CFEC /* MobileContentApiUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4538AE0C2C10981200A7CFEC /* MobileContentApiUITestsLaunchTests.swift */; }; - 4538AE1F2C1098A100A7CFEC /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4538AE1C2C1098A100A7CFEC /* ContentView.swift */; }; - 4538AE202C1098A100A7CFEC /* MobileContentApiApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4538AE1D2C1098A100A7CFEC /* MobileContentApiApp.swift */; }; - 4538AE222C1098B300A7CFEC /* MobileContentApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4538AE212C1098B300A7CFEC /* MobileContentApi.swift */; }; - 4538AE2B2C109A2000A7CFEC /* RequestOperation in Frameworks */ = {isa = PBXBuildFile; productRef = 4538AE2A2C109A2000A7CFEC /* RequestOperation */; }; - 4538AE342C109D1300A7CFEC /* MobileContentApiEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4538AE332C109D1300A7CFEC /* MobileContentApiEnvironment.swift */; }; - 45DF3C5E2C62B26C001B8522 /* MobileContentApiHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45DF3C5C2C62B26C001B8522 /* MobileContentApiHeaders.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 4538ADFD2C10981200A7CFEC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4538ADE42C10981100A7CFEC /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4538ADEB2C10981100A7CFEC; - remoteInfo = MobileContentApi; - }; - 4538AE072C10981200A7CFEC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4538ADE42C10981100A7CFEC /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4538ADEB2C10981100A7CFEC; - remoteInfo = MobileContentApi; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 450E55AC2C10E6D7000B96D0 /* ContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewModel.swift; sourceTree = ""; }; - 45208B2D2C626DF3000295F6 /* LanguageResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageResource.swift; sourceTree = ""; }; - 45208B312C626E0D000295F6 /* GetLanguagesEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLanguagesEndpoint.swift; sourceTree = ""; }; - 45208B332C626E14000295F6 /* GetLanguageEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetLanguageEndpoint.swift; sourceTree = ""; }; - 45208B362C626EE7000295F6 /* LanguageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageModel.swift; sourceTree = ""; }; - 4538ADEC2C10981100A7CFEC /* MobileContentApi.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MobileContentApi.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 4538ADF32C10981200A7CFEC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 4538ADF62C10981200A7CFEC /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 4538ADFC2C10981200A7CFEC /* MobileContentApiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MobileContentApiTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 4538AE002C10981200A7CFEC /* MobileContentApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileContentApiTests.swift; sourceTree = ""; }; - 4538AE062C10981200A7CFEC /* MobileContentApiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MobileContentApiUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 4538AE0A2C10981200A7CFEC /* MobileContentApiUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileContentApiUITests.swift; sourceTree = ""; }; - 4538AE0C2C10981200A7CFEC /* MobileContentApiUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileContentApiUITestsLaunchTests.swift; sourceTree = ""; }; - 4538AE1C2C1098A100A7CFEC /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 4538AE1D2C1098A100A7CFEC /* MobileContentApiApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MobileContentApiApp.swift; sourceTree = ""; }; - 4538AE212C1098B300A7CFEC /* MobileContentApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileContentApi.swift; sourceTree = ""; }; - 4538AE282C109A1700A7CFEC /* request-operation-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "request-operation-ios"; path = ../../..; sourceTree = ""; }; - 4538AE332C109D1300A7CFEC /* MobileContentApiEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileContentApiEnvironment.swift; sourceTree = ""; }; - 45DF3C5C2C62B26C001B8522 /* MobileContentApiHeaders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MobileContentApiHeaders.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 4538ADE92C10981100A7CFEC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4538AE2B2C109A2000A7CFEC /* RequestOperation in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4538ADF92C10981200A7CFEC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4538AE032C10981200A7CFEC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 45208B2A2C626CF0000295F6 /* Resources */ = { - isa = PBXGroup; - children = ( - 45208B2E2C626DF3000295F6 /* Language */, - ); - path = Resources; - sourceTree = ""; - }; - 45208B2E2C626DF3000295F6 /* Language */ = { - isa = PBXGroup; - children = ( - 45208B2D2C626DF3000295F6 /* LanguageResource.swift */, - 45208B302C626E03000295F6 /* Endpoints */, - 45208B352C626EDC000295F6 /* Models */, - ); - path = Language; - sourceTree = ""; - }; - 45208B302C626E03000295F6 /* Endpoints */ = { - isa = PBXGroup; - children = ( - 45208B332C626E14000295F6 /* GetLanguageEndpoint.swift */, - 45208B312C626E0D000295F6 /* GetLanguagesEndpoint.swift */, - ); - path = Endpoints; - sourceTree = ""; - }; - 45208B352C626EDC000295F6 /* Models */ = { - isa = PBXGroup; - children = ( - 45208B362C626EE7000295F6 /* LanguageModel.swift */, - ); - path = Models; - sourceTree = ""; - }; - 4538ADE32C10981100A7CFEC = { - isa = PBXGroup; - children = ( - 4538ADEE2C10981100A7CFEC /* MobileContentApi */, - 4538ADFF2C10981200A7CFEC /* MobileContentApiTests */, - 4538AE092C10981200A7CFEC /* MobileContentApiUITests */, - 4538ADED2C10981100A7CFEC /* Products */, - 4538AE292C109A2000A7CFEC /* Frameworks */, - ); - sourceTree = ""; - }; - 4538ADED2C10981100A7CFEC /* Products */ = { - isa = PBXGroup; - children = ( - 4538ADEC2C10981100A7CFEC /* MobileContentApi.app */, - 4538ADFC2C10981200A7CFEC /* MobileContentApiTests.xctest */, - 4538AE062C10981200A7CFEC /* MobileContentApiUITests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 4538ADEE2C10981100A7CFEC /* MobileContentApi */ = { - isa = PBXGroup; - children = ( - 4538AE232C10995F00A7CFEC /* Packages */, - 4538ADF32C10981200A7CFEC /* Assets.xcassets */, - 4538AE1E2C1098A100A7CFEC /* App */, - 4538ADF52C10981200A7CFEC /* Preview Content */, - ); - path = MobileContentApi; - sourceTree = ""; - }; - 4538ADF52C10981200A7CFEC /* Preview Content */ = { - isa = PBXGroup; - children = ( - 4538ADF62C10981200A7CFEC /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - 4538ADFF2C10981200A7CFEC /* MobileContentApiTests */ = { - isa = PBXGroup; - children = ( - 4538AE002C10981200A7CFEC /* MobileContentApiTests.swift */, - ); - path = MobileContentApiTests; - sourceTree = ""; - }; - 4538AE092C10981200A7CFEC /* MobileContentApiUITests */ = { - isa = PBXGroup; - children = ( - 4538AE0A2C10981200A7CFEC /* MobileContentApiUITests.swift */, - 4538AE0C2C10981200A7CFEC /* MobileContentApiUITestsLaunchTests.swift */, - ); - path = MobileContentApiUITests; - sourceTree = ""; - }; - 4538AE192C1098A100A7CFEC /* MobileContentApi */ = { - isa = PBXGroup; - children = ( - 4538AE212C1098B300A7CFEC /* MobileContentApi.swift */, - 4538AE332C109D1300A7CFEC /* MobileContentApiEnvironment.swift */, - 45DF3C5D2C62B26C001B8522 /* Headers */, - 45208B2A2C626CF0000295F6 /* Resources */, - ); - path = MobileContentApi; - sourceTree = ""; - }; - 4538AE1A2C1098A100A7CFEC /* Data */ = { - isa = PBXGroup; - children = ( - 4538AE192C1098A100A7CFEC /* MobileContentApi */, - ); - path = Data; - sourceTree = ""; - }; - 4538AE1B2C1098A100A7CFEC /* Share */ = { - isa = PBXGroup; - children = ( - 4538AE1A2C1098A100A7CFEC /* Data */, - ); - path = Share; - sourceTree = ""; - }; - 4538AE1E2C1098A100A7CFEC /* App */ = { - isa = PBXGroup; - children = ( - 4538AE1D2C1098A100A7CFEC /* MobileContentApiApp.swift */, - 4538AE1C2C1098A100A7CFEC /* ContentView.swift */, - 450E55AC2C10E6D7000B96D0 /* ContentViewModel.swift */, - 4538AE1B2C1098A100A7CFEC /* Share */, - ); - path = App; - sourceTree = ""; - }; - 4538AE232C10995F00A7CFEC /* Packages */ = { - isa = PBXGroup; - children = ( - 4538AE282C109A1700A7CFEC /* request-operation-ios */, - ); - path = Packages; - sourceTree = ""; - }; - 4538AE292C109A2000A7CFEC /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; - 45DF3C5D2C62B26C001B8522 /* Headers */ = { - isa = PBXGroup; - children = ( - 45DF3C5C2C62B26C001B8522 /* MobileContentApiHeaders.swift */, - ); - path = Headers; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 4538ADEB2C10981100A7CFEC /* MobileContentApi */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4538AE102C10981200A7CFEC /* Build configuration list for PBXNativeTarget "MobileContentApi" */; - buildPhases = ( - 4538ADE82C10981100A7CFEC /* Sources */, - 4538ADE92C10981100A7CFEC /* Frameworks */, - 4538ADEA2C10981100A7CFEC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = MobileContentApi; - packageProductDependencies = ( - 4538AE2A2C109A2000A7CFEC /* RequestOperation */, - ); - productName = MobileContentApi; - productReference = 4538ADEC2C10981100A7CFEC /* MobileContentApi.app */; - productType = "com.apple.product-type.application"; - }; - 4538ADFB2C10981200A7CFEC /* MobileContentApiTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4538AE132C10981200A7CFEC /* Build configuration list for PBXNativeTarget "MobileContentApiTests" */; - buildPhases = ( - 4538ADF82C10981200A7CFEC /* Sources */, - 4538ADF92C10981200A7CFEC /* Frameworks */, - 4538ADFA2C10981200A7CFEC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 4538ADFE2C10981200A7CFEC /* PBXTargetDependency */, - ); - name = MobileContentApiTests; - productName = MobileContentApiTests; - productReference = 4538ADFC2C10981200A7CFEC /* MobileContentApiTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 4538AE052C10981200A7CFEC /* MobileContentApiUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4538AE162C10981200A7CFEC /* Build configuration list for PBXNativeTarget "MobileContentApiUITests" */; - buildPhases = ( - 4538AE022C10981200A7CFEC /* Sources */, - 4538AE032C10981200A7CFEC /* Frameworks */, - 4538AE042C10981200A7CFEC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 4538AE082C10981200A7CFEC /* PBXTargetDependency */, - ); - name = MobileContentApiUITests; - productName = MobileContentApiUITests; - productReference = 4538AE062C10981200A7CFEC /* MobileContentApiUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 4538ADE42C10981100A7CFEC /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1530; - LastUpgradeCheck = 1530; - TargetAttributes = { - 4538ADEB2C10981100A7CFEC = { - CreatedOnToolsVersion = 15.3; - }; - 4538ADFB2C10981200A7CFEC = { - CreatedOnToolsVersion = 15.3; - TestTargetID = 4538ADEB2C10981100A7CFEC; - }; - 4538AE052C10981200A7CFEC = { - CreatedOnToolsVersion = 15.3; - TestTargetID = 4538ADEB2C10981100A7CFEC; - }; - }; - }; - buildConfigurationList = 4538ADE72C10981100A7CFEC /* Build configuration list for PBXProject "MobileContentApi" */; - compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 4538ADE32C10981100A7CFEC; - productRefGroup = 4538ADED2C10981100A7CFEC /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 4538ADEB2C10981100A7CFEC /* MobileContentApi */, - 4538ADFB2C10981200A7CFEC /* MobileContentApiTests */, - 4538AE052C10981200A7CFEC /* MobileContentApiUITests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 4538ADEA2C10981100A7CFEC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4538ADF72C10981200A7CFEC /* Preview Assets.xcassets in Resources */, - 4538ADF42C10981200A7CFEC /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4538ADFA2C10981200A7CFEC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4538AE042C10981200A7CFEC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 4538ADE82C10981100A7CFEC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4538AE222C1098B300A7CFEC /* MobileContentApi.swift in Sources */, - 4538AE202C1098A100A7CFEC /* MobileContentApiApp.swift in Sources */, - 4538AE342C109D1300A7CFEC /* MobileContentApiEnvironment.swift in Sources */, - 45208B2F2C626DF3000295F6 /* LanguageResource.swift in Sources */, - 45DF3C5E2C62B26C001B8522 /* MobileContentApiHeaders.swift in Sources */, - 45208B322C626E0D000295F6 /* GetLanguagesEndpoint.swift in Sources */, - 45208B342C626E14000295F6 /* GetLanguageEndpoint.swift in Sources */, - 45208B372C626EE7000295F6 /* LanguageModel.swift in Sources */, - 450E55AD2C10E6D7000B96D0 /* ContentViewModel.swift in Sources */, - 4538AE1F2C1098A100A7CFEC /* ContentView.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4538ADF82C10981200A7CFEC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4538AE012C10981200A7CFEC /* MobileContentApiTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4538AE022C10981200A7CFEC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4538AE0B2C10981200A7CFEC /* MobileContentApiUITests.swift in Sources */, - 4538AE0D2C10981200A7CFEC /* MobileContentApiUITestsLaunchTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 4538ADFE2C10981200A7CFEC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4538ADEB2C10981100A7CFEC /* MobileContentApi */; - targetProxy = 4538ADFD2C10981200A7CFEC /* PBXContainerItemProxy */; - }; - 4538AE082C10981200A7CFEC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4538ADEB2C10981100A7CFEC /* MobileContentApi */; - targetProxy = 4538AE072C10981200A7CFEC /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 4538AE0E2C10981200A7CFEC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 4538AE0F2C10981200A7CFEC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 4538AE112C10981200A7CFEC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"MobileContentApi/Preview Content\""; - DEVELOPMENT_TEAM = 9KUXNXY2SY; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = org.cru.MobileContentApi; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - }; - name = Debug; - }; - 4538AE122C10981200A7CFEC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"MobileContentApi/Preview Content\""; - DEVELOPMENT_TEAM = 9KUXNXY2SY; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = org.cru.MobileContentApi; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - }; - name = Release; - }; - 4538AE142C10981200A7CFEC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 9KUXNXY2SY; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = org.cru.MobileContentApiTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MobileContentApi.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MobileContentApi"; - }; - name = Debug; - }; - 4538AE152C10981200A7CFEC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 9KUXNXY2SY; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = org.cru.MobileContentApiTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MobileContentApi.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MobileContentApi"; - }; - name = Release; - }; - 4538AE172C10981200A7CFEC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 9KUXNXY2SY; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = org.cru.MobileContentApiUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - TEST_TARGET_NAME = MobileContentApi; - }; - name = Debug; - }; - 4538AE182C10981200A7CFEC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 9KUXNXY2SY; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = org.cru.MobileContentApiUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; - TEST_TARGET_NAME = MobileContentApi; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 4538ADE72C10981100A7CFEC /* Build configuration list for PBXProject "MobileContentApi" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4538AE0E2C10981200A7CFEC /* Debug */, - 4538AE0F2C10981200A7CFEC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4538AE102C10981200A7CFEC /* Build configuration list for PBXNativeTarget "MobileContentApi" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4538AE112C10981200A7CFEC /* Debug */, - 4538AE122C10981200A7CFEC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4538AE132C10981200A7CFEC /* Build configuration list for PBXNativeTarget "MobileContentApiTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4538AE142C10981200A7CFEC /* Debug */, - 4538AE152C10981200A7CFEC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4538AE162C10981200A7CFEC /* Build configuration list for PBXNativeTarget "MobileContentApiUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4538AE172C10981200A7CFEC /* Debug */, - 4538AE182C10981200A7CFEC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCSwiftPackageProductDependency section */ - 4538AE2A2C109A2000A7CFEC /* RequestOperation */ = { - isa = XCSwiftPackageProductDependency; - productName = RequestOperation; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = 4538ADE42C10981100A7CFEC /* Project object */; -} diff --git a/Example/MobileContentApi.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/MobileContentApi.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/Example/MobileContentApi.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Example/MobileContentApi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/MobileContentApi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/Example/MobileContentApi.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Example/MobileContentApi/App/ContentView.swift b/Example/MobileContentApi/App/ContentView.swift deleted file mode 100644 index 5972684..0000000 --- a/Example/MobileContentApi/App/ContentView.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// ContentView.swift -// MobileContentApi -// -// Created by Levi Eggert on 6/5/24. -// - -import SwiftUI - -struct ContentView: View { - - @ObservedObject private var viewModel: ContentViewModel - - init(viewModel: ContentViewModel) { - - self.viewModel = viewModel - } - - var body: some View { - - GeometryReader { geometry in - - VStack(alignment: .leading, spacing: 0) { - - HStack(alignment: .top, spacing: 0) { - Spacer() - VStack(alignment: .center, spacing: 5) { - - Image(systemName: "globe") - .imageScale(.large) - - Text("Hello, Mobile-Content API!") - } - Spacer() - } - .padding([.top], 30) - - VStack(alignment: .leading, spacing: 2) { - - Text("Language: \(viewModel.language?.code ?? "")") - Text(" name: \(viewModel.language?.name ?? "")") - - } - .padding([.top], 20) - - - - VStack(alignment: .leading, spacing: 5) { - - Text("Languages: \(viewModel.languages.count)") - - ScrollView(.vertical, showsIndicators: false) { - VStack(alignment: .leading, spacing: 2) { - - ForEach(0 ..< viewModel.languages.count, id: \.self) { index in - Text(" language: \(viewModel.languages[index])") - } - } - } - } - .padding([.top], 20) - } - } - } -} diff --git a/Example/MobileContentApi/App/ContentViewModel.swift b/Example/MobileContentApi/App/ContentViewModel.swift deleted file mode 100644 index 8d27ce3..0000000 --- a/Example/MobileContentApi/App/ContentViewModel.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// ContentViewModel.swift -// MobileContentApi -// -// Created by Levi Eggert on 6/5/24. -// - -import Foundation -import Combine - -class ContentViewModel: ObservableObject { - - private let mobileContentApi: MobileContentApi - - private var cancellables: Set = Set() - - @Published var language: LanguageModel? - @Published var languages: [String] = Array() - - init(mobileContentApi: MobileContentApi) { - - self.mobileContentApi = mobileContentApi - - getLanguages() - - getLanguage() - } - - private func getLanguages() { - - mobileContentApi - .languageResource - .getLanguagesEndpoint - .getLanguages() - .receive(on: DispatchQueue.main) - .sink { [weak self] (languages: [LanguageModel]) in - - self?.languages = languages.map({ - $0.name - }) - } - .store(in: &cancellables) - } - - private func getLanguage() { - - mobileContentApi - .languageResource - .getLanguageEndpoint - .getLanguage(id: "4") - .receive(on: DispatchQueue.main) - .sink { [weak self] (language: LanguageModel?) in - - self?.language = language - } - .store(in: &cancellables) - } -} diff --git a/Example/MobileContentApi/App/MobileContentApiApp.swift b/Example/MobileContentApi/App/MobileContentApiApp.swift deleted file mode 100644 index 9f95bcb..0000000 --- a/Example/MobileContentApi/App/MobileContentApiApp.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// MobileContentApiApp.swift -// MobileContentApi -// -// Created by Levi Eggert on 6/5/24. -// - -import SwiftUI - -@main -struct MobileContentApiApp: App { - var body: some Scene { - WindowGroup { - ContentView( - viewModel: ContentViewModel( - mobileContentApi: MobileContentApi(environment: .staging) - ) - ) - } - } -} diff --git a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Headers/MobileContentApiHeaders.swift b/Example/MobileContentApi/App/Share/Data/MobileContentApi/Headers/MobileContentApiHeaders.swift deleted file mode 100644 index 9ecd19a..0000000 --- a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Headers/MobileContentApiHeaders.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// MobileContentApiHeaders.swift -// MobileContentApi -// -// Created by Levi Eggert on 6/5/24. -// - -import Foundation -import RequestOperation - -class MobileContentApiHeaders { - - static let defaultAuthorizationKey: String = "Authorization" - static let defaultContentTypeKey: String = "Content-Type" - static let defaultContentTypeValue: String = "application/vnd.api+json" - - let authorizationKey: String - let authorizationValue: String? - let contentTypeKey: String - let contentTypeValue: String? - let additionalHeaders: [String: String]? - - convenience init(authorizationValue: String? = nil, contentTypeValue: String = MobileContentApiHeaders.defaultContentTypeValue, additionalHeaders: [String: String]? = nil) { - - self.init( - authorizationKey: MobileContentApiHeaders.defaultAuthorizationKey, - authorizationValue: authorizationValue, - contentTypeKey: MobileContentApiHeaders.defaultContentTypeKey, - contentTypeValue: contentTypeValue, - additionalHeaders: nil - ) - } - - init(authorizationKey: String, authorizationValue: String?, contentTypeKey: String, contentTypeValue: String?, additionalHeaders: [String: String]?) { - - self.authorizationKey = authorizationKey - self.authorizationValue = authorizationValue - self.contentTypeKey = contentTypeKey - self.contentTypeValue = contentTypeValue - self.additionalHeaders = additionalHeaders - } - - static func authorizedHeaders(authorizationValue: String) -> MobileContentApiHeaders { - return MobileContentApiHeaders(authorizationValue: authorizationValue) - } - - static func nonAuthorizedHeaders() -> MobileContentApiHeaders { - return MobileContentApiHeaders() - } - - func getHeadersValue() -> [String: String] { - - var headers: [String: String] = additionalHeaders ?? Dictionary() - - if let contentType = contentTypeValue { - headers[contentTypeKey] = contentType - } - - if let authorizationValue = authorizationValue { - headers[authorizationKey] = authorizationValue - } - - return headers - } -} diff --git a/Example/MobileContentApi/App/Share/Data/MobileContentApi/MobileContentApi.swift b/Example/MobileContentApi/App/Share/Data/MobileContentApi/MobileContentApi.swift deleted file mode 100644 index d2f9b7b..0000000 --- a/Example/MobileContentApi/App/Share/Data/MobileContentApi/MobileContentApi.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// MobileContentApi.swift -// MobileContentApi -// -// Created by Levi Eggert on 6/5/24. -// - -import Foundation -import RequestOperation - -class MobileContentApi: RequestApi { - - let languageResource: LanguageResource - - init(environment: MobileContentApiEnvironment) { - - let baseUrl: ApiBaseUrl = environment.baseUrl - - let urlSession: URLSession = RequestUrlSession.sharedIgnoreCacheSession - - let requestController = RequestController( - requestBuilder: RequestBuilder(), - requestSender: RequestSender(), - requestRetrier: nil - ) - - let context = RequestApiSharedContext( - baseUrl: baseUrl, - urlSession: urlSession, - requestController: requestController - ) - - languageResource = LanguageResource(context: context) - - super.init( - context: context, - resources: [languageResource] - ) - } -} diff --git a/Example/MobileContentApi/App/Share/Data/MobileContentApi/MobileContentApiEnvironment.swift b/Example/MobileContentApi/App/Share/Data/MobileContentApi/MobileContentApiEnvironment.swift deleted file mode 100644 index 55beeb8..0000000 --- a/Example/MobileContentApi/App/Share/Data/MobileContentApi/MobileContentApiEnvironment.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// MobileContentApiEnvironment.swift -// MobileContentApi -// -// Created by Levi Eggert on 6/5/24. -// - -import Foundation -import RequestOperation - -enum MobileContentApiEnvironment { - - case staging - case production - - var host: String { - switch self { - case .staging: - return "mobile-content-api-stage.cru.org" - case .production: - return "mobile-content-api.cru.org" - } - } -} - -extension MobileContentApiEnvironment { - var baseUrl: ApiBaseUrl { - switch self { - case .staging: - return ApiBaseUrl(scheme: .https, host: host) - case .production: - return ApiBaseUrl(scheme: .https, host: host) - } - } -} diff --git a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Endpoints/GetLanguageEndpoint.swift b/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Endpoints/GetLanguageEndpoint.swift deleted file mode 100644 index b247155..0000000 --- a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Endpoints/GetLanguageEndpoint.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// GetLanguageEndpoint.swift -// MobileContentApi -// -// Created by Levi Eggert on 8/6/24. -// - -import Foundation -import RequestOperation -import Combine - -class GetLanguageEndpoint: ApiEndpoint { - - private let urlSession: URLSession - - init(urlSession: URLSession, resourceUrl: ApiResourceUrl, context: RequestApiSharedContext) { - - self.urlSession = urlSession - - super.init(resourceUrl: resourceUrl, requestController: context.requestController) - } - - func getLanguage(id: String) -> AnyPublisher { - - return requestController.buildAndSendRequestPublisher( - urlSession: urlSession, - urlString: resourceUrl.absoluteUrl + "/" + id, - method: .get, - headers: MobileContentApiHeaders.nonAuthorizedHeaders().getHeadersValue(), - httpBody: nil, - queryItems: nil - ) - .map { (response: RequestCodableResponse, NoResponseCodable>) in - return response.successCodable?.dataObject - } - .catch { _ in - return Just(nil) - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } -} diff --git a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Endpoints/GetLanguagesEndpoint.swift b/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Endpoints/GetLanguagesEndpoint.swift deleted file mode 100644 index 831f282..0000000 --- a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Endpoints/GetLanguagesEndpoint.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// GetLanguagesEndpoint.swift -// MobileContentApi -// -// Created by Levi Eggert on 8/6/24. -// - -import Foundation -import RequestOperation -import Combine - -class GetLanguagesEndpoint: ApiEndpoint { - - private let urlSession: URLSession - - init(urlSession: URLSession, resourceUrl: ApiResourceUrl, context: RequestApiSharedContext) { - - self.urlSession = urlSession - - super.init(resourceUrl: resourceUrl, requestController: context.requestController) - } - - func getLanguages() -> AnyPublisher<[LanguageModel], Never> { - - return requestController.buildAndSendRequestPublisher( - urlSession: urlSession, - urlString: resourceUrl.absoluteUrl, - method: .get, - headers: MobileContentApiHeaders.nonAuthorizedHeaders().getHeadersValue(), - httpBody: nil, - queryItems: nil - ) - .map { (response: RequestCodableResponse, NoResponseCodable>) in - response.successCodable?.dataArray ?? [] - } - .catch { _ in - return Just([]) - .eraseToAnyPublisher() - } - .eraseToAnyPublisher() - } -} diff --git a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/LanguageResource.swift b/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/LanguageResource.swift deleted file mode 100644 index 6c2ac16..0000000 --- a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/LanguageResource.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// LanguageResource.swift -// MobileContentApi -// -// Created by Levi Eggert on 8/6/24. -// - -import Foundation -import RequestOperation - -class LanguageResource: ApiResource { - - let getLanguageEndpoint: GetLanguageEndpoint - let getLanguagesEndpoint: GetLanguagesEndpoint - - init(context: RequestApiSharedContext) { - - let resourceUrl = ApiResourceUrl(baseUrl: context.baseUrl, path: "languages") - - getLanguageEndpoint = GetLanguageEndpoint( - urlSession: context.urlSession, - resourceUrl: resourceUrl, - context: context - ) - - getLanguagesEndpoint = GetLanguagesEndpoint( - urlSession: context.urlSession, - resourceUrl: resourceUrl, - context: context - ) - - super.init(resourceUrl: resourceUrl, endpoints: [], context: context) - } -} diff --git a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Models/LanguageModel.swift b/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Models/LanguageModel.swift deleted file mode 100644 index d2a3aa2..0000000 --- a/Example/MobileContentApi/App/Share/Data/MobileContentApi/Resources/Language/Models/LanguageModel.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// LanguageModel.swift -// MobileContentApi -// -// Created by Levi Eggert on 8/6/24. -// - -import Foundation - -struct LanguageModel: Codable { - - let code: String - let direction: String - let id: String - let name: String - let type: String - - enum RootKeys: String, CodingKey { - case id = "id" - case type = "type" - case attributes = "attributes" - } - - enum AttributesKeys: String, CodingKey { - case code = "code" - case name = "name" - case direction = "direction" - } - - init(from decoder: Decoder) throws { - - let container = try decoder.container(keyedBy: RootKeys.self) - - id = try container.decode(String.self, forKey: .id) - type = try container.decode(String.self, forKey: .type) - - var attributesContainer: KeyedDecodingContainer? - - do { - attributesContainer = try container.nestedContainer(keyedBy: AttributesKeys.self, forKey: .attributes) - } - catch { - // It's possible that a Language doesn't have an attributes key. - } - - // attributes - code = try attributesContainer?.decodeIfPresent(String.self, forKey: .code) ?? "" - direction = try attributesContainer?.decodeIfPresent(String.self, forKey: .direction) ?? "" - name = try attributesContainer?.decodeIfPresent(String.self, forKey: .name) ?? "" - } -} - -extension LanguageModel: Equatable { - static func ==(lhs: LanguageModel, rhs: LanguageModel) -> Bool { - return lhs.id == rhs.id - } -} - diff --git a/Example/MobileContentApi/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/MobileContentApi/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/Example/MobileContentApi/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example/MobileContentApi/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/MobileContentApi/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 13613e3..0000000 --- a/Example/MobileContentApi/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example/MobileContentApi/Assets.xcassets/Contents.json b/Example/MobileContentApi/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/Example/MobileContentApi/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example/MobileContentApi/Preview Content/Preview Assets.xcassets/Contents.json b/Example/MobileContentApi/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/Example/MobileContentApi/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example/MobileContentApiTests/MobileContentApiTests.swift b/Example/MobileContentApiTests/MobileContentApiTests.swift deleted file mode 100644 index df6aabd..0000000 --- a/Example/MobileContentApiTests/MobileContentApiTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// MobileContentApiTests.swift -// MobileContentApiTests -// -// Created by Levi Eggert on 6/5/24. -// - -import XCTest -@testable import MobileContentApi - -final class MobileContentApiTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Example/MobileContentApiUITests/MobileContentApiUITests.swift b/Example/MobileContentApiUITests/MobileContentApiUITests.swift deleted file mode 100644 index f3bdf7a..0000000 --- a/Example/MobileContentApiUITests/MobileContentApiUITests.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// MobileContentApiUITests.swift -// MobileContentApiUITests -// -// Created by Levi Eggert on 6/5/24. -// - -import XCTest - -final class MobileContentApiUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/Example/MobileContentApiUITests/MobileContentApiUITestsLaunchTests.swift b/Example/MobileContentApiUITests/MobileContentApiUITestsLaunchTests.swift deleted file mode 100644 index 36a149e..0000000 --- a/Example/MobileContentApiUITests/MobileContentApiUITestsLaunchTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// MobileContentApiUITestsLaunchTests.swift -// MobileContentApiUITests -// -// Created by Levi Eggert on 6/5/24. -// - -import XCTest - -final class MobileContentApiUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} diff --git a/Package.swift b/Package.swift index d472bc6..f2ebf1e 100644 --- a/Package.swift +++ b/Package.swift @@ -21,8 +21,7 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "RequestOperation", - dependencies: [], - exclude: ["../../Example"] + dependencies: [] ), .testTarget( name: "RequestOperationTests", diff --git a/README.md b/README.md index 2a36089..b8e4a2c 100644 --- a/README.md +++ b/README.md @@ -3,47 +3,18 @@ RequestOperation ================ -A swift package to facilitate in creation of URLRequests (RequestBuilder) and provides an Operation (RequestOperation) for submitting requests from an OperationQueue. Completed RequestOperations will provide a RequestResponse object with some details about the completed request. +A swift package to facilitate in the creation and sending of URLRequests. + +Highlights: +- Build URLRequests with RequestBuilder. +- Send URLRequests with RequestSender. +- Create URLSession Configuration via CreateUrlSessionConfigInterface. +- Prioritize URLRequests with URLSessionPriority. +- Decode request response for success or failure. -- [Basic Usage](#basic-usage) -- [Advanced Usage](#advanced-usage) - [Publishing New Versions With GitHub Actions](#publishing-new-versions-with-github-actions) - [Publishing New Versions Manually](#publishing-new-versions-manually) -### Basic Usage - -- Use RequestBuilder to build URLRequests - -```swift -let urlRequest: URLRequest = RequestBuilder().build( - parameters: RequestBuilderParameters( - urlSession: urlSession, - urlString: "url-here", - method: .get, - headers: nil, - httpBody: nil, - queryItems: nil - ) -) -``` - -- Use RequestSender to send URLRequests - -```swift -let requestSender = RequestSender() - -return requestSender.sendDataTaskPublisher( - urlRequest: urlRequest, - urlSession: urlSession -) -.map { (response: RequestDataResponse) in - // Do something with response... -} -.eraseToAnyPublisher() -``` - -### Advanced Usage - ### Publishing New Versions With GitHub Actions Publishing new versions with GitHub Actions build workflow. diff --git a/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseBaseData.swift b/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseBaseData.swift index 0882412..3d1d0f2 100644 --- a/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseBaseData.swift +++ b/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseBaseData.swift @@ -8,7 +8,7 @@ import Foundation -public struct JsonApiResponseBaseData: Codable { +public struct JsonApiResponseBaseData: Codable, Sendable { public let id: String public let type: String diff --git a/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataArray.swift b/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataArray.swift index c08401c..6125190 100644 --- a/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataArray.swift +++ b/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataArray.swift @@ -8,7 +8,7 @@ import Foundation -public struct JsonApiResponseDataArray: Codable { +public struct JsonApiResponseDataArray: Codable, Sendable { public let dataArray: [T] diff --git a/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataObject.swift b/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataObject.swift index b9524ac..3a501fe 100644 --- a/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataObject.swift +++ b/Sources/RequestOperation/Codable/JsonApi/JsonApiResponseDataObject.swift @@ -8,7 +8,7 @@ import Foundation -public struct JsonApiResponseDataObject: Codable { +public struct JsonApiResponseDataObject: Codable, Sendable { public let dataObject: T diff --git a/Sources/RequestOperation/RequestBuilder/RequestBuilder.swift b/Sources/RequestOperation/RequestBuilder/RequestBuilder.swift index edebb1f..4d5dda9 100644 --- a/Sources/RequestOperation/RequestBuilder/RequestBuilder.swift +++ b/Sources/RequestOperation/RequestBuilder/RequestBuilder.swift @@ -8,7 +8,7 @@ import Foundation -open class RequestBuilder { +public final class RequestBuilder: Sendable { public let requestMutators: [RequestMutator] @@ -22,12 +22,12 @@ open class RequestBuilder { self.requestMutators = requestMutators ?? requestBuilder.requestMutators } - open func clone(requestMutators: [RequestMutator]? = nil) -> RequestBuilder { + public func clone(requestMutators: [RequestMutator]? = nil) -> RequestBuilder { return RequestBuilder(requestBuilder: self, requestMutators: requestMutators ?? self.requestMutators) } - open func build(parameters: RequestBuilderParameters) -> URLRequest { + public func build(parameters: RequestBuilderParameters) -> URLRequest { let url: URL? @@ -51,23 +51,13 @@ open class RequestBuilder { return URLRequest(url: url!) } - let result: Result = build( + return build( url: url, parameters: parameters ) - - switch result { - - case .success(let request): - return request - - case .failure(let error): - assertionFailure(error.localizedDescription) - return URLRequest(url: url) - } } - open func build(url: URL, parameters: RequestBuilderParameters) -> Result { + public func build(url: URL, parameters: RequestBuilderParameters) -> URLRequest { var urlRequest = URLRequest( url: url, @@ -83,22 +73,13 @@ open class RequestBuilder { } urlRequest.httpMethod = parameters.method.rawValue - - if let httpBody = parameters.httpBody { - - do { - urlRequest.httpBody = try JSONSerialization.data(withJSONObject: httpBody, options: []) - } - catch let error { - return .failure(error) - } - } + urlRequest.httpBody = parameters.httpBody for requestMutator in requestMutators { requestMutator.mutate(request: &urlRequest, parameters: parameters) } - return .success(urlRequest) + return urlRequest } } diff --git a/Sources/RequestOperation/RequestBuilder/RequestBuilderParameters.swift b/Sources/RequestOperation/RequestBuilder/RequestBuilderParameters.swift index 72ccaf7..4b626b0 100644 --- a/Sources/RequestOperation/RequestBuilder/RequestBuilderParameters.swift +++ b/Sources/RequestOperation/RequestBuilder/RequestBuilderParameters.swift @@ -8,19 +8,19 @@ import Foundation -open class RequestBuilderParameters { +public final class RequestBuilderParameters: Sendable { public let requestCachePolicy: NSURLRequest.CachePolicy public let timeoutIntervalForRequest: TimeInterval public let urlString: String public let method: RequestMethod public let headers: [String: String]? - public let httpBody: [String: Any]? + public let httpBody: Data? public let queryItems: [URLQueryItem]? - public convenience init(urlSession: URLSession, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?, timeoutIntervalForRequest: TimeInterval? = nil) { + public convenience init(urlSession: URLSession, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?, timeoutIntervalForRequest: TimeInterval? = nil) throws { - self.init( + try self.init( requestCachePolicy: urlSession.configuration.requestCachePolicy, timeoutIntervalForRequest: timeoutIntervalForRequest ?? urlSession.configuration.timeoutIntervalForRequest, urlString: urlString, @@ -31,9 +31,9 @@ open class RequestBuilderParameters { ) } - public convenience init(configuration: URLSessionConfiguration, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?, timeoutIntervalForRequest: TimeInterval? = nil) { + public convenience init(configuration: URLSessionConfiguration, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?, timeoutIntervalForRequest: TimeInterval? = nil) throws { - self.init( + try self.init( requestCachePolicy: configuration.requestCachePolicy, timeoutIntervalForRequest: timeoutIntervalForRequest ?? configuration.timeoutIntervalForRequest, urlString: urlString, @@ -44,14 +44,32 @@ open class RequestBuilderParameters { ) } - public init(requestCachePolicy: NSURLRequest.CachePolicy, timeoutIntervalForRequest: TimeInterval, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?) { + public init(requestCachePolicy: NSURLRequest.CachePolicy, timeoutIntervalForRequest: TimeInterval, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?) throws { self.requestCachePolicy = requestCachePolicy self.timeoutIntervalForRequest = timeoutIntervalForRequest self.urlString = urlString self.method = method self.headers = headers - self.httpBody = httpBody + self.httpBody = try Self.getHttpBodyData(httpBody: httpBody) self.queryItems = queryItems } + + private static func getHttpBodyData(httpBody: [String: Any]?) throws -> Data? { + + guard let httpBody = httpBody, !httpBody.isEmpty else { + return nil + } + + return try JSONSerialization.data(withJSONObject: httpBody, options: []) + } + + public func getHttpBodyObject(options: JSONSerialization.ReadingOptions = []) throws -> [String: Any] { + + guard let httpBody = self.httpBody else { + return Dictionary() + } + + return try JSONSerialization.jsonObject(with: httpBody, options: options) as? [String: Any] ?? Dictionary() + } } diff --git a/Sources/RequestOperation/RequestBuilder/RequestMethod.swift b/Sources/RequestOperation/RequestBuilder/RequestMethod.swift index 10683e5..de90c68 100644 --- a/Sources/RequestOperation/RequestBuilder/RequestMethod.swift +++ b/Sources/RequestOperation/RequestBuilder/RequestMethod.swift @@ -8,7 +8,7 @@ import Foundation -public enum RequestMethod: String { +public enum RequestMethod: String, Sendable { case options = "OPTIONS" case get = "GET" diff --git a/Sources/RequestOperation/RequestBuilder/RequestMutator/RequestMutator.swift b/Sources/RequestOperation/RequestBuilder/RequestMutator/RequestMutator.swift index 8e9bd7a..8ac42a7 100644 --- a/Sources/RequestOperation/RequestBuilder/RequestMutator/RequestMutator.swift +++ b/Sources/RequestOperation/RequestBuilder/RequestMutator/RequestMutator.swift @@ -8,7 +8,7 @@ import Foundation -public protocol RequestMutator { +public protocol RequestMutator: Sendable { func mutate(request: inout URLRequest, parameters: RequestBuilderParameters) } diff --git a/Sources/RequestOperation/RequestController/RequestController.swift b/Sources/RequestOperation/RequestController/RequestController.swift index 25ab316..932ab99 100644 --- a/Sources/RequestOperation/RequestController/RequestController.swift +++ b/Sources/RequestOperation/RequestController/RequestController.swift @@ -7,9 +7,8 @@ // import Foundation -import Combine -open class RequestController { +public final class RequestController: Sendable { public let requestBuilder: RequestBuilder public let requestSender: RequestSender @@ -22,9 +21,18 @@ open class RequestController { self.requestRetrier = requestRetrier } - open func buildAndSendRequestPublisher(urlSession: URLSession, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?, timeoutIntervalForRequest: TimeInterval? = nil, decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher, Error> { + public func buildAndSendRequest( + urlSession: URLSession, + urlString: String, + method: RequestMethod, + headers: [String: String]?, + httpBody: [String: Any]?, + queryItems: [URLQueryItem]?, + timeoutIntervalForRequest: TimeInterval? = nil, + decoder: JSONDecoder = JSONDecoder() + ) async throws -> RequestCodableResponse { - return internalBuildAndSendRequestPublisher( + return try await internalBuildAndSendRequest( urlSession: urlSession, urlString: urlString, method: method, @@ -34,13 +42,20 @@ open class RequestController { timeoutIntervalForRequest: timeoutIntervalForRequest ) .decodeRequestDataResponseForSuccessOrFailureCodable(decoder: decoder) - .eraseToAnyPublisher() } - private func internalBuildAndSendRequestPublisher(urlSession: URLSession, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?, timeoutIntervalForRequest: TimeInterval?) -> AnyPublisher { + private func internalBuildAndSendRequest( + urlSession: URLSession, + urlString: String, + method: RequestMethod, + headers: [String: String]?, + httpBody: [String: Any]?, + queryItems: [URLQueryItem]?, + timeoutIntervalForRequest: TimeInterval? + ) async throws -> RequestDataResponse { let urlRequest: URLRequest = requestBuilder.build( - parameters: RequestBuilderParameters( + parameters: try RequestBuilderParameters( urlSession: urlSession, urlString: urlString, method: method, @@ -51,69 +66,60 @@ open class RequestController { ) ) - return requestSender - .sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .flatMap({ (response: RequestDataResponse) -> AnyPublisher in - - return self.retryRequestIfNeededPublisher( - response: response, - urlSession: urlSession, - urlString: urlString, - method: method, - headers: headers, - httpBody: httpBody, - queryItems: queryItems, - timeoutIntervalForRequest: timeoutIntervalForRequest - ) - }) - .eraseToAnyPublisher() + let response = try await requestSender + .sendDataTask( + urlRequest: urlRequest, + urlSession: urlSession + ) + + return try await retryRequestIfNeeded( + response: response, + urlSession: urlSession, + urlString: urlString, + method: method, + headers: headers, + httpBody: httpBody, + queryItems: queryItems, + timeoutIntervalForRequest: timeoutIntervalForRequest + ) } - private func retryRequestIfNeededPublisher(response: RequestDataResponse, urlSession: URLSession, urlString: String, method: RequestMethod, headers: [String: String]?, httpBody: [String: Any]?, queryItems: [URLQueryItem]?, timeoutIntervalForRequest: TimeInterval?) -> AnyPublisher { + private func retryRequestIfNeeded( + response: RequestDataResponse, + urlSession: URLSession, + urlString: String, + method: RequestMethod, + headers: [String: String]?, + httpBody: [String: Any]?, + queryItems: [URLQueryItem]?, + timeoutIntervalForRequest: TimeInterval? + ) async throws -> RequestDataResponse { guard let requestRetrier = self.requestRetrier else { - return Just(response) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + return response } - return requestRetrier.shouldRetryRequestPublisher( + let retryPolicy: RetryPolicy = requestRetrier.shouldRetryRequest( response: response, httpStatusCode: response.urlResponse.httpStatusCode, isSuccessHttpStatusCode: response.urlResponse.isSuccessHttpStatusCode ) - .setFailureType(to: Error.self) - .flatMap({ [weak self] (retryPolicy: RetryPolicy) -> AnyPublisher in - - guard let weakSelf = self else { - - return Just(response) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } + + switch retryPolicy { + + case .doNotRetry: + return response - switch retryPolicy { - - case .doNotRetry: - - return Just(response) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - - case .retry( _): - - return weakSelf.internalBuildAndSendRequestPublisher( - urlSession: urlSession, - urlString: urlString, - method: method, - headers: headers, - httpBody: httpBody, - queryItems: queryItems, - timeoutIntervalForRequest: timeoutIntervalForRequest - ) - .eraseToAnyPublisher() - } - }) - .eraseToAnyPublisher() + case .retry( _): + return try await internalBuildAndSendRequest( + urlSession: urlSession, + urlString: urlString, + method: method, + headers: headers, + httpBody: httpBody, + queryItems: queryItems, + timeoutIntervalForRequest: timeoutIntervalForRequest + ) + } } } diff --git a/Sources/RequestOperation/RequestController/RequestRetrier/RequestRetrier.swift b/Sources/RequestOperation/RequestController/RequestRetrier/RequestRetrier.swift index 424e746..b8a4444 100644 --- a/Sources/RequestOperation/RequestController/RequestRetrier/RequestRetrier.swift +++ b/Sources/RequestOperation/RequestController/RequestRetrier/RequestRetrier.swift @@ -7,9 +7,8 @@ // import Foundation -import Combine -public protocol RequestRetrier { +public protocol RequestRetrier: Sendable { - func shouldRetryRequestPublisher(response: RequestDataResponse, httpStatusCode: Int?, isSuccessHttpStatusCode: Bool) -> AnyPublisher + func shouldRetryRequest(response: RequestDataResponse, httpStatusCode: Int?, isSuccessHttpStatusCode: Bool) -> RetryPolicy } diff --git a/Sources/RequestOperation/RequestController/RequestRetrier/RetryParameters.swift b/Sources/RequestOperation/RequestController/RequestRetrier/RetryParameters.swift index ca4791b..3632042 100644 --- a/Sources/RequestOperation/RequestController/RequestRetrier/RetryParameters.swift +++ b/Sources/RequestOperation/RequestController/RequestRetrier/RetryParameters.swift @@ -8,7 +8,7 @@ import Foundation -open class RetryParameters { +public struct RetryParameters: Sendable { public let delaySeconds: TimeInterval? diff --git a/Sources/RequestOperation/RequestController/RequestRetrier/RetryPolicy.swift b/Sources/RequestOperation/RequestController/RequestRetrier/RetryPolicy.swift index 011c68a..80e4145 100644 --- a/Sources/RequestOperation/RequestController/RequestRetrier/RetryPolicy.swift +++ b/Sources/RequestOperation/RequestController/RequestRetrier/RetryPolicy.swift @@ -8,7 +8,7 @@ import Foundation -public enum RetryPolicy { +public enum RetryPolicy: Sendable { case doNotRetry case retry(parameters: RetryParameters) diff --git a/Sources/RequestOperation/RequestSender/Operation/RequestOperation.swift b/Sources/RequestOperation/RequestSender/Operation/RequestOperation.swift deleted file mode 100644 index 7269eb8..0000000 --- a/Sources/RequestOperation/RequestSender/Operation/RequestOperation.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// RequestOperation.swift -// request-operation-ios -// -// Created by Levi Eggert on 1/3/2022. -// Copyright © 2020 Cru. All rights reserved. -// - -import Foundation - -open class RequestOperation: Operation, @unchecked Sendable { - - public typealias Completion = ((_ response: RequestOperationResponse) -> Void) - - enum ObserverKey: String { - case isExecuting = "isExecuting" - case isFinshed = "isFinished" - } - - enum State { - case executing - case finished - case notStarted - } - - private let errorDomain: String = String(describing: RequestOperation.self) - - private var task: URLSessionDataTask? - private var completion: Completion? - - private(set) var urlRequest: URLRequest - - public let session: URLSession - - public init(session: URLSession, urlRequest: URLRequest) { - self.session = session - self.urlRequest = urlRequest - super.init() - } - - public func setUrlRequestHeader(httpHeaderField: String, value: String) { - urlRequest.setValue(value, forHTTPHeaderField: httpHeaderField) - } - - public func setCompletionHandler(completion: @escaping Completion) { - self.completion = completion - } - - open override func start() { - - guard !isCancelled else { - handleOperationCancelled() - return - } - - task = session.dataTask(with: urlRequest) { [weak self] (data: Data?, urlResponse: URLResponse?, error: Error?) in - - self?.completeOperation(data: data, urlResponse: urlResponse, requestError: error) - } - - task?.resume() - state = .executing - } - - open override func cancel() { - super.cancel() - task?.cancel() - } - - open func completeOperation(data: Data?, urlResponse: URLResponse?, requestError: Error?) { - - state = .finished - - let requestDataResponse: RequestDataResponse? - - if let data = data, let urlResponse = urlResponse { - requestDataResponse = RequestDataResponse(data: data, urlResponse: urlResponse) - } - else { - requestDataResponse = nil - } - - let response = RequestOperationResponse( - urlRequest: urlRequest, - requestDataResponse: requestDataResponse, - requestError: requestError - ) - - completion?(response) - } - - private func handleOperationCancelled() { - - let cancelledError: Error = NSError( - domain: errorDomain, - code: NSURLErrorCancelled, - userInfo: [NSLocalizedDescriptionKey: "The operation was cancelled."] - ) - - completeOperation(data: nil, urlResponse: nil, requestError: cancelledError) - } - - // MARK: - State - - public override var isAsynchronous: Bool { - return true - } - - public override var isExecuting: Bool { - return state == .executing - } - - public override var isFinished: Bool { - return state == .finished - } - - private var state: State = .notStarted { - willSet (value) { - switch value { - case .executing: - willChangeValue(forKey: ObserverKey.isExecuting.rawValue) - case .finished: - willChangeValue(forKey: ObserverKey.isFinshed.rawValue) - case .notStarted: - break - } - } - didSet { - switch state { - case .executing: - didChangeValue(forKey: ObserverKey.isExecuting.rawValue) - case .finished: - didChangeValue(forKey: ObserverKey.isFinshed.rawValue) - case .notStarted: - break - } - } - } -} diff --git a/Sources/RequestOperation/RequestSender/Operation/RequestOperationResponse.swift b/Sources/RequestOperation/RequestSender/Operation/RequestOperationResponse.swift deleted file mode 100644 index d1a99f3..0000000 --- a/Sources/RequestOperation/RequestSender/Operation/RequestOperationResponse.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// RequestOperationResponse.swift -// request-operation-ios -// -// Created by Levi Eggert on 5/27/2025. -// Copyright © 2025 Cru. All rights reserved. -// - -import Foundation - -open class RequestOperationResponse { - - public let urlRequest: URLRequest - public let requestDataResponse: RequestDataResponse? - public let requestError: Error? - - init(urlRequest: URLRequest, requestDataResponse: RequestDataResponse?, requestError: Error?) { - - self.urlRequest = urlRequest - self.requestDataResponse = requestDataResponse - self.requestError = requestError - } -} diff --git a/Sources/RequestOperation/RequestSender/RequestSender.swift b/Sources/RequestOperation/RequestSender/RequestSender.swift index e63efa3..3677523 100644 --- a/Sources/RequestOperation/RequestSender/RequestSender.swift +++ b/Sources/RequestOperation/RequestSender/RequestSender.swift @@ -7,38 +7,33 @@ // import Foundation -import Combine -open class RequestSender { +public final class RequestSender: Sendable { public init() { } - open func sendDataTask(urlRequest: URLRequest, urlSession: URLSession) async throws -> RequestDataResponse { + public func sendDataTask(urlRequest: URLRequest, urlSession: URLSession) async throws -> RequestDataResponse { - let tuple: (data: Data, response: URLResponse) = try await urlSession.data(for: urlRequest) - - let response = RequestDataResponse( - data: tuple.data, - urlResponse: tuple.response - ) - - return response - } - - open func sendDataTaskPublisher(urlRequest: URLRequest, urlSession: URLSession) -> AnyPublisher { - - return urlSession.dataTaskPublisher(for: urlRequest) - .map { (tuple: (data: Data, response: URLResponse)) in - RequestDataResponse( - data: tuple.data, - urlResponse: tuple.response - ) - } - .mapError { (urlError: URLError) in - return urlError.toError() + do { + + let tuple: (data: Data, response: URLResponse) = try await urlSession.data(for: urlRequest) + + let response = RequestDataResponse( + data: tuple.data, + urlResponse: tuple.response + ) + + return response + } + catch let error { + + if let urlError = error as? URLError { + throw urlError.toError() } - .eraseToAnyPublisher() + + throw error + } } } diff --git a/Sources/RequestOperation/RequestSender/Response/Decode/Publisher+RequestDataDecoder.swift b/Sources/RequestOperation/RequestSender/Response/Decode/Publisher+RequestDataDecoder.swift deleted file mode 100644 index f1c7c82..0000000 --- a/Sources/RequestOperation/RequestSender/Response/Decode/Publisher+RequestDataDecoder.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Publisher+RequestDataDecoder.swift -// request-operation-ios -// -// Created by Levi Eggert on 8/16/2024. -// Copyright © 2024 Cru. All rights reserved. -// - -import Foundation -import Combine - -extension Publisher where Output == RequestDataResponse, Failure == Error { - - public func decodeRequestDataResponseForSuccessCodable(decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher, Error> { - - self.tryMap { (response: RequestDataResponse) in - - return try response.decodeRequestDataResponseForSuccessCodable( - decoder: decoder - ) - } - .eraseToAnyPublisher() - } - - public func decodeRequestDataResponseForSuccessOrFailureCodable(decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher, Error> { - - self.tryMap { (response: RequestDataResponse) in - - return try response.decodeRequestDataResponseForSuccessOrFailureCodable( - decoder: decoder - ) - } - .eraseToAnyPublisher() - } -} diff --git a/Sources/RequestOperation/RequestSender/Response/Decode/RequestDataResponse+Decode.swift b/Sources/RequestOperation/RequestSender/Response/Decode/RequestDataResponse+Decode.swift index c4f0868..f7a947f 100644 --- a/Sources/RequestOperation/RequestSender/Response/Decode/RequestDataResponse+Decode.swift +++ b/Sources/RequestOperation/RequestSender/Response/Decode/RequestDataResponse+Decode.swift @@ -11,82 +11,45 @@ extension RequestDataResponse { public func decodeRequestDataResponseForSuccessCodable(decoder: JSONDecoder = JSONDecoder()) throws -> RequestCodableResponse { - let successCodable: SuccessCodable? - let failureCodable: NoResponseCodable = NoResponseCodable() - - if urlResponse.isSuccessHttpStatusCode { - - do { - - let object: SuccessCodable? = try decoder.decode(SuccessCodable.self, from: data) - - successCodable = object - } - catch let decodeError { - - successCodable = nil - - throw decodeError - } - } - else { + guard urlResponse.isSuccessHttpStatusCode else { - successCodable = nil + return RequestCodableResponse( + successCodable: nil, + failureCodable: NoResponseCodable(), + requestDataResponse: self + ) } - let codableResponse = RequestCodableResponse( + let successCodable: SuccessCodable? = try decoder.decode(SuccessCodable.self, from: data) + + return RequestCodableResponse( successCodable: successCodable, - failureCodable: failureCodable, + failureCodable: NoResponseCodable(), requestDataResponse: self ) - - return codableResponse } public func decodeRequestDataResponseForSuccessOrFailureCodable(decoder: JSONDecoder = JSONDecoder()) throws -> RequestCodableResponse { - - let successCodable: SuccessCodable? - let failureCodable: FailureCodable? if urlResponse.isSuccessHttpStatusCode { - failureCodable = nil + let successCodable: SuccessCodable? = try decoder.decode(SuccessCodable.self, from: data) - do { - - let object: SuccessCodable? = try decoder.decode(SuccessCodable.self, from: data) - - successCodable = object - } - catch let decodeError { - - successCodable = nil - - throw decodeError - } + return RequestCodableResponse( + successCodable: successCodable, + failureCodable: nil, + requestDataResponse: self + ) } else { - successCodable = nil - - do { - - let object: FailureCodable? = try decoder.decode(FailureCodable.self, from: data) - - failureCodable = object - } - catch let decodeError { - - failureCodable = nil - - throw decodeError - } + let failureCodable: FailureCodable? = try decoder.decode(FailureCodable.self, from: data) + + return RequestCodableResponse( + successCodable: nil, + failureCodable: failureCodable, + requestDataResponse: self + ) } - - return RequestCodableResponse( - successCodable: successCodable, - failureCodable: failureCodable, - requestDataResponse: self - ) } } diff --git a/Sources/RequestOperation/RequestSender/Response/NoResponseCodable.swift b/Sources/RequestOperation/RequestSender/Response/NoResponseCodable.swift index 98f01ad..5d30dcc 100644 --- a/Sources/RequestOperation/RequestSender/Response/NoResponseCodable.swift +++ b/Sources/RequestOperation/RequestSender/Response/NoResponseCodable.swift @@ -8,6 +8,6 @@ import Foundation -public struct NoResponseCodable: Codable { +public struct NoResponseCodable: Codable, Sendable { } diff --git a/Sources/RequestOperation/RequestSender/Response/RequestCodableResponse.swift b/Sources/RequestOperation/RequestSender/Response/RequestCodableResponse.swift index aebea08..2652b12 100644 --- a/Sources/RequestOperation/RequestSender/Response/RequestCodableResponse.swift +++ b/Sources/RequestOperation/RequestSender/Response/RequestCodableResponse.swift @@ -8,7 +8,7 @@ import Foundation -open class RequestCodableResponse { +public final class RequestCodableResponse: Sendable { public let successCodable: SuccessCodable? public let failureCodable: FailureCodable? diff --git a/Sources/RequestOperation/RequestSender/Response/RequestDataResponse.swift b/Sources/RequestOperation/RequestSender/Response/RequestDataResponse.swift index 056d02a..25d438d 100644 --- a/Sources/RequestOperation/RequestSender/Response/RequestDataResponse.swift +++ b/Sources/RequestOperation/RequestSender/Response/RequestDataResponse.swift @@ -8,7 +8,7 @@ import Foundation -open class RequestDataResponse { +public final class RequestDataResponse: Sendable { public let data: Data public let urlResponse: URLResponse diff --git a/Sources/RequestOperation/RequestSender/Response/URLResponse+HttpStatusCode.swift b/Sources/RequestOperation/RequestSender/Response/URLResponse+HttpStatusCode.swift index 38e6e1b..58e05ee 100644 --- a/Sources/RequestOperation/RequestSender/Response/URLResponse+HttpStatusCode.swift +++ b/Sources/RequestOperation/RequestSender/Response/URLResponse+HttpStatusCode.swift @@ -25,8 +25,8 @@ extension URLResponse { return Self.getIsSuccessHttpStatusCode(httpStatusCode: httpStatusCode) } - public static func getIsSuccessHttpStatusCode(httpStatusCode: Int) -> Bool { + public static func getIsSuccessHttpStatusCode(httpStatusCode: Int, successRange: Range = URLResponse.httpStatusCodeSuccessRange) -> Bool { - return URLResponse.httpStatusCodeSuccessRange.contains(httpStatusCode) + return successRange.contains(httpStatusCode) } } diff --git a/Sources/RequestOperation/RequestSender/Response/Validate/Publisher+Validate.swift b/Sources/RequestOperation/RequestSender/Response/Validate/Publisher+Validate.swift deleted file mode 100644 index 7d57df6..0000000 --- a/Sources/RequestOperation/RequestSender/Response/Validate/Publisher+Validate.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Publisher+Validate.swift -// request-operation-ios -// -// Created by Levi Eggert on 9/17/2024. -// Copyright © 2024 Cru. All rights reserved. -// - -import Foundation -import Combine - -extension Publisher where Output == RequestDataResponse, Failure == Error { - - public func validate(successRange: Range = URLResponse.httpStatusCodeSuccessRange) -> AnyPublisher { - - self.tryMap { (response: RequestDataResponse) in - - return try response.validate() - } - .eraseToAnyPublisher() - } -} diff --git a/Sources/RequestOperation/RequestSender/Response/Validate/RequestDataResponse+Validate.swift b/Sources/RequestOperation/RequestSender/Response/Validate/RequestDataResponse+Validate.swift index 838d5e2..aba53fb 100644 --- a/Sources/RequestOperation/RequestSender/Response/Validate/RequestDataResponse+Validate.swift +++ b/Sources/RequestOperation/RequestSender/Response/Validate/RequestDataResponse+Validate.swift @@ -9,9 +9,9 @@ import Foundation extension RequestDataResponse { - public func validate() throws -> RequestDataResponse { + public func validate(successRange: Range = URLResponse.httpStatusCodeSuccessRange) throws -> RequestDataResponse { - guard let httpStatusCode = urlResponse.httpStatusCode, URLResponse.getIsSuccessHttpStatusCode(httpStatusCode: httpStatusCode) else { + guard let httpStatusCode = urlResponse.httpStatusCode, URLResponse.getIsSuccessHttpStatusCode(httpStatusCode: httpStatusCode, successRange: successRange) else { throw toError() } diff --git a/Sources/RequestOperation/UrlQueryItem/JsonApi/JsonApiFilter.swift b/Sources/RequestOperation/UrlQueryItem/JsonApi/JsonApiFilter.swift index 34cd01a..a6336c0 100644 --- a/Sources/RequestOperation/UrlQueryItem/JsonApi/JsonApiFilter.swift +++ b/Sources/RequestOperation/UrlQueryItem/JsonApi/JsonApiFilter.swift @@ -8,7 +8,7 @@ import Foundation -public struct JsonApiFilter { +public struct JsonApiFilter: Sendable { public typealias Name = String public typealias Value = String diff --git a/Sources/RequestOperation/UrlSession/Config/CreateUrlSessionConfigInterface.swift b/Sources/RequestOperation/UrlSession/Config/CreateUrlSessionConfigInterface.swift index b643771..799074e 100644 --- a/Sources/RequestOperation/UrlSession/Config/CreateUrlSessionConfigInterface.swift +++ b/Sources/RequestOperation/UrlSession/Config/CreateUrlSessionConfigInterface.swift @@ -7,7 +7,7 @@ import Foundation -public protocol CreateUrlSessionConfigInterface { +public protocol CreateUrlSessionConfigInterface: Sendable { func createConfig(timeoutIntervalForRequest: TimeInterval) -> URLSessionConfiguration } diff --git a/Sources/RequestOperation/UrlSession/CreateUrlSession.swift b/Sources/RequestOperation/UrlSession/CreateUrlSession.swift new file mode 100644 index 0000000..570aeb7 --- /dev/null +++ b/Sources/RequestOperation/UrlSession/CreateUrlSession.swift @@ -0,0 +1,28 @@ +// +// CreateUrlSession.swift +// request-operation-ios +// +// Created by Levi Eggert on 5/29/2024. +// Copyright © 2024 Cru. All rights reserved. +// + +import Foundation + +public final class CreateUrlSession: Sendable { + + public init() { + + } + + public func createSession(config: CreateUrlSessionConfigInterface, timeoutIntervalForRequest: TimeInterval) -> URLSession { + return URLSession( + configuration: config.createConfig(timeoutIntervalForRequest: timeoutIntervalForRequest) + ) + } + + public func createIgnoreCacheSession(timeoutIntervalForRequest: TimeInterval) -> URLSession { + return URLSession( + configuration: CreateIgnoreCacheSessionConfig().createConfig(timeoutIntervalForRequest: timeoutIntervalForRequest) + ) + } +} diff --git a/Sources/RequestOperation/UrlSession/Priority/URLSessionPriority.swift b/Sources/RequestOperation/UrlSession/Priority/URLSessionPriority.swift index 55e2515..f8d99ad 100644 --- a/Sources/RequestOperation/UrlSession/Priority/URLSessionPriority.swift +++ b/Sources/RequestOperation/UrlSession/Priority/URLSessionPriority.swift @@ -7,7 +7,7 @@ import Foundation -public final class URLSessionPriority { +public final class URLSessionPriority: Sendable { private let lowPriorityQueue: URLSessionQueue private let mediumPriorityQueue: URLSessionQueue diff --git a/Sources/RequestOperation/UrlSession/Priority/URLSessionQueue.swift b/Sources/RequestOperation/UrlSession/Priority/URLSessionQueue.swift index e85e053..c5dd4df 100644 --- a/Sources/RequestOperation/UrlSession/Priority/URLSessionQueue.swift +++ b/Sources/RequestOperation/UrlSession/Priority/URLSessionQueue.swift @@ -7,7 +7,7 @@ import Foundation -public final class URLSessionQueue { +public final class URLSessionQueue: Sendable { private let operationQueue: OperationQueue private let urlSession: URLSession diff --git a/Sources/RequestOperation/UrlSession/RequestUrlSession.swift b/Sources/RequestOperation/UrlSession/RequestUrlSession.swift deleted file mode 100644 index 90d654e..0000000 --- a/Sources/RequestOperation/UrlSession/RequestUrlSession.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// RequestUrlSession.swift -// request-operation-ios -// -// Created by Levi Eggert on 5/29/2024. -// Copyright © 2024 Cru. All rights reserved. -// - -import Foundation - -public final class RequestUrlSession { - - public static let sharedIgnoreCacheSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: 60) - - public static func createIgnoreCacheSession(timeoutIntervalForRequest: TimeInterval = 60) -> URLSession { - return URLSession( - configuration: CreateIgnoreCacheSessionConfig().createConfig(timeoutIntervalForRequest: timeoutIntervalForRequest) - ) - } -} diff --git a/Tests/RequestOperationTests/RequestBuilder/RequestBuilderParametersTests.swift b/Tests/RequestOperationTests/RequestBuilder/RequestBuilderParametersTests.swift index fa64a30..c3b8cb0 100644 --- a/Tests/RequestOperationTests/RequestBuilder/RequestBuilderParametersTests.swift +++ b/Tests/RequestOperationTests/RequestBuilder/RequestBuilderParametersTests.swift @@ -6,13 +6,14 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation +import Foundation -class RequestBuilderParametersTests: XCTestCase { +struct RequestBuilderParametersTests { - func testInitWithURLSession() { - + @Test func initWithURLSession() throws { + let requestCachePolicy: NSURLRequest.CachePolicy = .reloadIgnoringCacheData let timeoutIntervalSeconds: TimeInterval = 30 let urlString: String = "url-string" @@ -33,7 +34,7 @@ class RequestBuilderParametersTests: XCTestCase { let session = URLSession(configuration: configuration) - let parameters = RequestBuilderParameters( + let parameters = try RequestBuilderParameters( urlSession: session, urlString: urlString, method: method, @@ -43,18 +44,20 @@ class RequestBuilderParametersTests: XCTestCase { timeoutIntervalForRequest: nil ) - XCTAssertTrue(parameters.requestCachePolicy == requestCachePolicy) - XCTAssertTrue(parameters.timeoutIntervalForRequest == timeoutIntervalSeconds) - XCTAssertTrue(parameters.urlString == urlString) - XCTAssertTrue(parameters.method == method) - XCTAssertTrue(parameters.headers == headers) - XCTAssertTrue(parameters.httpBody?.count == 1) - XCTAssertTrue(parameters.httpBody?["param_1"] as? Int == 1) - XCTAssertTrue(parameters.queryItems == queryItems) + let httpBodyObject: [String: Any] = try parameters.getHttpBodyObject() + + #expect(parameters.requestCachePolicy == requestCachePolicy) + #expect(parameters.timeoutIntervalForRequest == timeoutIntervalSeconds) + #expect(parameters.urlString == urlString) + #expect(parameters.method == method) + #expect(parameters.headers == headers) + #expect(httpBodyObject.count == 1) + #expect(httpBodyObject["param_1"] as? Int == 1) + #expect(parameters.queryItems == queryItems) } - func testInitWithURLSessionUsesProvidedTimeoutIntervalForRequest() { - + @Test func initWithURLSessionUsesProvidedTimeoutIntervalForRequest() throws { + let requestCachePolicy: NSURLRequest.CachePolicy = .reloadIgnoringCacheData let urlString: String = "url-string" let method: RequestMethod = .delete @@ -76,7 +79,7 @@ class RequestBuilderParametersTests: XCTestCase { let providedTimeoutIntervalSeconds: TimeInterval = 30 - let parameters = RequestBuilderParameters( + let parameters = try RequestBuilderParameters( urlSession: session, urlString: urlString, method: method, @@ -86,18 +89,20 @@ class RequestBuilderParametersTests: XCTestCase { timeoutIntervalForRequest: providedTimeoutIntervalSeconds ) - XCTAssertTrue(parameters.requestCachePolicy == requestCachePolicy) - XCTAssertTrue(parameters.timeoutIntervalForRequest == providedTimeoutIntervalSeconds) - XCTAssertTrue(parameters.urlString == urlString) - XCTAssertTrue(parameters.method == method) - XCTAssertTrue(parameters.headers == headers) - XCTAssertTrue(parameters.httpBody?.count == 1) - XCTAssertTrue(parameters.httpBody?["param_1"] as? Int == 1) - XCTAssertTrue(parameters.queryItems == queryItems) + let httpBodyObject: [String: Any] = try parameters.getHttpBodyObject() + + #expect(parameters.requestCachePolicy == requestCachePolicy) + #expect(parameters.timeoutIntervalForRequest == providedTimeoutIntervalSeconds) + #expect(parameters.urlString == urlString) + #expect(parameters.method == method) + #expect(parameters.headers == headers) + #expect(httpBodyObject.count == 1) + #expect(httpBodyObject["param_1"] as? Int == 1) + #expect(parameters.queryItems == queryItems) } - - func testInitWithURLSessionConfiguration() { + @Test func initWithURLSessionConfiguration() throws { + let requestCachePolicy: NSURLRequest.CachePolicy = .reloadIgnoringCacheData let timeoutIntervalSeconds: TimeInterval = 30 let urlString: String = "url-string" @@ -116,7 +121,7 @@ class RequestBuilderParametersTests: XCTestCase { configuration.timeoutIntervalForRequest = timeoutIntervalSeconds - let parameters = RequestBuilderParameters( + let parameters = try RequestBuilderParameters( configuration: configuration, urlString: urlString, method: method, @@ -126,18 +131,20 @@ class RequestBuilderParametersTests: XCTestCase { timeoutIntervalForRequest: nil ) - XCTAssertTrue(parameters.requestCachePolicy == requestCachePolicy) - XCTAssertTrue(parameters.timeoutIntervalForRequest == timeoutIntervalSeconds) - XCTAssertTrue(parameters.urlString == urlString) - XCTAssertTrue(parameters.method == method) - XCTAssertTrue(parameters.headers == headers) - XCTAssertTrue(parameters.httpBody?.count == 1) - XCTAssertTrue(parameters.httpBody?["param_1"] as? Int == 1) - XCTAssertTrue(parameters.queryItems == queryItems) + let httpBodyObject: [String: Any] = try parameters.getHttpBodyObject() + + #expect(parameters.requestCachePolicy == requestCachePolicy) + #expect(parameters.timeoutIntervalForRequest == timeoutIntervalSeconds) + #expect(parameters.urlString == urlString) + #expect(parameters.method == method) + #expect(parameters.headers == headers) + #expect(httpBodyObject.count == 1) + #expect(httpBodyObject["param_1"] as? Int == 1) + #expect(parameters.queryItems == queryItems) } - - func testInitWithURLSessionConfigurationUsesProvidedTimeoutIntervalForRequest() { + @Test func initWithURLSessionConfigurationUsesProvidedTimeoutIntervalForRequest() throws { + let requestCachePolicy: NSURLRequest.CachePolicy = .reloadIgnoringCacheData let urlString: String = "url-string" let method: RequestMethod = .delete @@ -157,7 +164,7 @@ class RequestBuilderParametersTests: XCTestCase { let providedTimeoutIntervalSeconds: TimeInterval = 30 - let parameters = RequestBuilderParameters( + let parameters = try RequestBuilderParameters( configuration: configuration, urlString: urlString, method: method, @@ -167,13 +174,15 @@ class RequestBuilderParametersTests: XCTestCase { timeoutIntervalForRequest: providedTimeoutIntervalSeconds ) - XCTAssertTrue(parameters.requestCachePolicy == requestCachePolicy) - XCTAssertTrue(parameters.timeoutIntervalForRequest == providedTimeoutIntervalSeconds) - XCTAssertTrue(parameters.urlString == urlString) - XCTAssertTrue(parameters.method == method) - XCTAssertTrue(parameters.headers == headers) - XCTAssertTrue(parameters.httpBody?.count == 1) - XCTAssertTrue(parameters.httpBody?["param_1"] as? Int == 1) - XCTAssertTrue(parameters.queryItems == queryItems) + let httpBodyObject: [String: Any] = try parameters.getHttpBodyObject() + + #expect(parameters.requestCachePolicy == requestCachePolicy) + #expect(parameters.timeoutIntervalForRequest == providedTimeoutIntervalSeconds) + #expect(parameters.urlString == urlString) + #expect(parameters.method == method) + #expect(parameters.headers == headers) + #expect(httpBodyObject.count == 1) + #expect(httpBodyObject["param_1"] as? Int == 1) + #expect(parameters.queryItems == queryItems) } } diff --git a/Tests/RequestOperationTests/RequestBuilder/RequestBuilderTests.swift b/Tests/RequestOperationTests/RequestBuilder/RequestBuilderTests.swift index bfd4eda..99c2337 100644 --- a/Tests/RequestOperationTests/RequestBuilder/RequestBuilderTests.swift +++ b/Tests/RequestOperationTests/RequestBuilder/RequestBuilderTests.swift @@ -6,71 +6,127 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation +import Foundation -class RequestBuilderTests: XCTestCase { +struct RequestBuilderTests { - - func testDefaultParametersAreSet() { + @Test func defaultParametersAreSet() async { let requestBuilder = RequestBuilder() - XCTAssertTrue(requestBuilder.requestMutators.isEmpty) + #expect(requestBuilder.requestMutators.isEmpty) } - func testCloneWithoutMutators() { + @Test func initWithRequestBuilderFallsBackToRequestBuilderRequestMutators() async { + + let requestBuilderA = RequestBuilder(requestMutators: [TestRequestMutatorA(), TestRequestMutatorB()]) + + let requestBuilderB = RequestBuilder(requestBuilder: requestBuilderA, requestMutators: nil) + + #expect(requestBuilderA == requestBuilderB) + } + + @Test func cloneWithoutMutators() async { let requestBuilderWithoutMutators = RequestBuilder(requestMutators: []) - XCTAssertTrue(requestBuilderWithoutMutators == requestBuilderWithoutMutators.clone()) + #expect(requestBuilderWithoutMutators == requestBuilderWithoutMutators.clone()) } - func testCloneWithMutators() { + @Test func cloneWithMutators() async { let requestBuilderWithMutators = RequestBuilder(requestMutators: [TestRequestMutatorA(), TestRequestMutatorB()]) - XCTAssertTrue(requestBuilderWithMutators == requestBuilderWithMutators.clone()) + #expect(requestBuilderWithMutators == requestBuilderWithMutators.clone()) } - func testRequestBuilderMutatorsAreEqual() { + @Test func requestBuilderMutatorsAreEqual() async { let requestBuilderA = RequestBuilder(requestMutators: [TestRequestMutatorA()]) let requestBuilderB = RequestBuilder(requestMutators: [TestRequestMutatorA()]) - XCTAssertTrue(requestBuilderA == requestBuilderB) + #expect(requestBuilderA == requestBuilderB) } - func testRequestBuilderMutatorsAreNotEqual() { + @Test func requestBuilderMutatorsAreNotEqual() async { let requestBuilderA = RequestBuilder(requestMutators: [TestRequestMutatorA()]) let requestBuilderB = RequestBuilder(requestMutators: [TestRequestMutatorB()]) - XCTAssertTrue(requestBuilderA != requestBuilderB) + #expect(requestBuilderA != requestBuilderB) } - func testBuildUrlRequest() { + @Test func requestBuilderMutatorsCountIsNotEqual() async { + + let requestBuilderA = RequestBuilder(requestMutators: [TestRequestMutatorA()]) + let requestBuilderB = RequestBuilder(requestMutators: [TestRequestMutatorA(), TestRequestMutatorB()]) + + #expect(requestBuilderA != requestBuilderB) + } + + @Test func buildUrlRequestFromString() async throws { let requestBuilder = RequestBuilder() let urlString: String = "https://mobile-content-api-stage.cru.org/languages/4" let requestMethod: RequestMethod = .get let contentType: String = "application/vnd.api+json" + let httpBody: [String: Any] = ["key_1": 1, "key_2": "2", "key_3": 3.0, "key_4": false] - let urlRequest: URLRequest = requestBuilder.build( + let urlRequest: URLRequest = try requestBuilder.build( parameters: RequestBuilderParameters( requestCachePolicy: .reloadIgnoringCacheData, timeoutIntervalForRequest: 60, urlString: urlString, method: requestMethod, headers: ["Content-Type": contentType], - httpBody: nil, + httpBody: httpBody, queryItems: nil ) ) - - XCTAssertTrue(urlRequest.url?.absoluteString == urlString) - XCTAssertTrue(urlRequest.httpMethod == requestMethod.rawValue) - XCTAssertTrue(urlRequest.allHTTPHeaderFields?["Content-Type"] == contentType) + + let urlRequestHttpBodyData: Data = try #require(urlRequest.httpBody) + + let urlRequestHttpBody: [String: Any] = try JSONSerialization.jsonObject(with: urlRequestHttpBodyData, options: []) as? [String: Any] ?? Dictionary() + + #expect(urlRequest.url?.absoluteString == urlString) + #expect(urlRequest.httpMethod == requestMethod.rawValue) + #expect(urlRequest.allHTTPHeaderFields?["Content-Type"] == contentType) + #expect(urlRequestHttpBody.count == httpBody.count) + #expect(Array(urlRequestHttpBody.keys).sorted() == Array(httpBody.keys).sorted()) + } + + @Test func buildUrlRequestFromStringWithUrlQuery() async throws { + + let requestBuilder = RequestBuilder( + requestMutators: [TestRequestMutatorA()] + ) + + let urlString: String = "https://mobile-content-api-stage.cru.org/languages/4" + let requestMethod: RequestMethod = .get + let contentType: String = "application/vnd.api+json" + let queryItems: [URLQueryItem] = [ + URLQueryItem(name: "param_1", value: "1") + ] + + let urlRequest: URLRequest = try requestBuilder.build( + parameters: RequestBuilderParameters( + requestCachePolicy: .reloadIgnoringCacheData, + timeoutIntervalForRequest: 60, + urlString: urlString, + method: requestMethod, + headers: ["Content-Type": contentType], + httpBody: nil, + queryItems: queryItems + ) + ) + + let urlStringWithQuery: String = "https://mobile-content-api-stage.cru.org/languages/4" + "?param_1=1" + + #expect(urlRequest.url?.absoluteString == urlStringWithQuery) + #expect(urlRequest.httpMethod == requestMethod.rawValue) + #expect(urlRequest.allHTTPHeaderFields?["Content-Type"] == contentType) } } diff --git a/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorA.swift b/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorA.swift index 01f981f..e5467b2 100644 --- a/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorA.swift +++ b/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorA.swift @@ -6,10 +6,10 @@ // Copyright © 2024 Cru. All rights reserved. // -import Foundation @testable import RequestOperation +import Foundation -class TestRequestMutatorA: RequestMutator { +final class TestRequestMutatorA: RequestMutator { func mutate(request: inout URLRequest, parameters: RequestBuilderParameters) { diff --git a/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorB.swift b/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorB.swift index a0345b4..e55f8d6 100644 --- a/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorB.swift +++ b/Tests/RequestOperationTests/RequestBuilder/TestRequestMutatorB.swift @@ -6,10 +6,10 @@ // Copyright © 2024 Cru. All rights reserved. // -import Foundation @testable import RequestOperation +import Foundation -class TestRequestMutatorB: RequestMutator { +final class TestRequestMutatorB: RequestMutator { func mutate(request: inout URLRequest, parameters: RequestBuilderParameters) { diff --git a/Tests/RequestOperationTests/RequestController/RequestControllerTests.swift b/Tests/RequestOperationTests/RequestController/RequestControllerTests.swift index dd40c40..7871990 100644 --- a/Tests/RequestOperationTests/RequestController/RequestControllerTests.swift +++ b/Tests/RequestOperationTests/RequestController/RequestControllerTests.swift @@ -6,9 +6,10 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation +import Foundation -class RequestControllerTests: XCTestCase { +struct RequestControllerTests { } diff --git a/Tests/RequestOperationTests/RequestController/RequestRetrier/RetryParametersTests.swift b/Tests/RequestOperationTests/RequestController/RequestRetrier/RetryParametersTests.swift index 58a4ece..2fcb7f1 100644 --- a/Tests/RequestOperationTests/RequestController/RequestRetrier/RetryParametersTests.swift +++ b/Tests/RequestOperationTests/RequestController/RequestRetrier/RetryParametersTests.swift @@ -6,24 +6,27 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation +import Foundation -class RetryParametersTests: XCTestCase { +struct RetryParametersTests { - func testDefaultParametersAreSet() { + @Test + func defaultParametersAreSet() { let retryParameters = RetryParameters() - XCTAssertNil(retryParameters.delaySeconds) + #expect(retryParameters.delaySeconds == nil) } - func testDelaySecondsParameterIsSet() { + @Test + func delaySecondsParameterIsSet() { let delaySeconds: TimeInterval = 5 let retryParameters = RetryParameters(delaySeconds: delaySeconds) - XCTAssertTrue(retryParameters.delaySeconds == delaySeconds) + #expect(retryParameters.delaySeconds == delaySeconds) } } diff --git a/Tests/RequestOperationTests/RequestSender/Codable/InvalidLanguageCodable.swift b/Tests/RequestOperationTests/RequestSender/Codable/InvalidLanguageCodable.swift index 334e9b2..8931768 100644 --- a/Tests/RequestOperationTests/RequestSender/Codable/InvalidLanguageCodable.swift +++ b/Tests/RequestOperationTests/RequestSender/Codable/InvalidLanguageCodable.swift @@ -8,7 +8,7 @@ import Foundation -struct InvalidLanguageCodable: Codable { +struct InvalidLanguageCodable: Codable, Sendable { let code: String let id: String diff --git a/Tests/RequestOperationTests/RequestSender/Codable/LanguageCodable.swift b/Tests/RequestOperationTests/RequestSender/Codable/LanguageCodable.swift index 2639b35..b25f313 100644 --- a/Tests/RequestOperationTests/RequestSender/Codable/LanguageCodable.swift +++ b/Tests/RequestOperationTests/RequestSender/Codable/LanguageCodable.swift @@ -1,5 +1,5 @@ // -// LanguageModel.swift +// LanguageCodable.swift // request-operation-ios // // Created by Levi Eggert on 9/13/2024. @@ -8,7 +8,7 @@ import Foundation -struct LanguageModel: Codable { +struct LanguageCodable: Codable, Sendable { let code: String let directionString: String diff --git a/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorCodable.swift b/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorCodable.swift index a870ca0..1e58b55 100644 --- a/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorCodable.swift +++ b/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorCodable.swift @@ -8,7 +8,7 @@ import Foundation -struct MobileContentApiErrorCodable: Codable { +struct MobileContentApiErrorCodable: Codable, Sendable { let detail: String diff --git a/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorsCodable.swift b/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorsCodable.swift index 2ad06d3..737f1cd 100644 --- a/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorsCodable.swift +++ b/Tests/RequestOperationTests/RequestSender/Codable/MobileContentApiErrorsCodable.swift @@ -8,7 +8,7 @@ import Foundation -struct MobileContentApiErrorsCodable: Codable { +struct MobileContentApiErrorsCodable: Codable, Sendable { let errors: [MobileContentApiErrorCodable] diff --git a/Tests/RequestOperationTests/RequestSender/Operation/RequestOperationTests.swift b/Tests/RequestOperationTests/RequestSender/Operation/RequestOperationTests.swift deleted file mode 100644 index b69f5ff..0000000 --- a/Tests/RequestOperationTests/RequestSender/Operation/RequestOperationTests.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// RequestOperationTests.swift -// request-operation-ios -// -// Created by Levi Eggert on 5/27/2025. -// Copyright © 2025 Cru. All rights reserved. -// - -import XCTest -@testable import RequestOperation - -class RequestOperationTests: XCTestCase { - - func testSendUrlRequestIsSuccessful() { - - let timeoutSeconds: TimeInterval = 15 - - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = RequestSenderTests.buildGetLanguageUrlRequest( - urlSession: urlSession, - languageId: RequestSenderTests.englishLanguageId - ) - - let queue = OperationQueue() - - let requestOperation = RequestOperation( - session: urlSession, - urlRequest: urlRequest - ) - - let expectation = expectation(description: "") - - var responseRef: RequestOperationResponse? - - requestOperation.setCompletionHandler { (response: RequestOperationResponse) in - - responseRef = response - - expectation.fulfill() - } - - queue.addOperations([requestOperation], waitUntilFinished: false) - - wait(for: [expectation], timeout: timeoutSeconds) - - XCTAssertNotNil(responseRef) - XCTAssertTrue(responseRef!.requestDataResponse!.urlResponse.isSuccessHttpStatusCode) - } - - func testSendUrlRequestIsUnSuccessful() { - - let timeoutSeconds: TimeInterval = 15 - - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = RequestSenderTests.buildGetLanguageUrlRequest( - urlSession: urlSession, - languageId: RequestSenderTests.invalidLanguageId - ) - - let queue = OperationQueue() - - let requestOperation = RequestOperation( - session: urlSession, - urlRequest: urlRequest - ) - - let expectation = expectation(description: "") - - var responseRef: RequestOperationResponse? - - requestOperation.setCompletionHandler { (response: RequestOperationResponse) in - - responseRef = response - - expectation.fulfill() - } - - queue.addOperations([requestOperation], waitUntilFinished: false) - - wait(for: [expectation], timeout: timeoutSeconds) - - XCTAssertNotNil(responseRef) - XCTAssertFalse(responseRef!.requestDataResponse!.urlResponse.isSuccessHttpStatusCode) - } -} diff --git a/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Decode.swift b/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Decode.swift index 2f6649f..5f0f239 100644 --- a/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Decode.swift +++ b/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Decode.swift @@ -6,200 +6,138 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation -import Combine +import Foundation extension RequestSenderTests { - func testSuccessfullyDecodeSuccessCodableWhenRequestIsSuccessful() { + @Test() + func successfullyDecodeSuccessCodableWhenRequestIsSuccessful() async throws { - let timeoutSeconds: TimeInterval = 15 - - let languageId: String = Self.englishLanguageId - - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: languageId) - let requestSender = RequestSender() - let expectation = expectation(description: "") + let languageId: String = englishLanguageId - var responseRef: RequestCodableResponse, NoResponseCodable>? - - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .decodeRequestDataResponseForSuccessCodable() - .sink { completion in - - expectation.fulfill() + let urlRequest: URLRequest = try getLanguageUrlRequest( + languageId: languageId + ) - } receiveValue: { (response: RequestCodableResponse, NoResponseCodable>) in - - responseRef = response - } - .store(in: &cancellables) + let response: RequestCodableResponse, NoResponseCodable> = try await requestSender.sendDataTask( + urlRequest: urlRequest, + urlSession: urlSession + ).decodeRequestDataResponseForSuccessCodable() - wait(for: [expectation], timeout: timeoutSeconds) + let successCodable: JsonApiResponseDataObject = try #require(response.successCodable) - XCTAssertNotNil(responseRef) - XCTAssertNotNil(responseRef?.successCodable) - XCTAssertTrue(responseRef?.successCodable?.dataObject.id == languageId) + #expect(successCodable.dataObject.id == languageId) } - func testSuccessCodableIsNilWhenRequestIsUnSuccessful() { - - let timeoutSeconds: TimeInterval = 15 - - let languageId: String = Self.invalidLanguageId + @Test() + func successCodableIsNilWhenRequestIsUnSuccessful() async throws { - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: languageId) - let requestSender = RequestSender() - let expectation = expectation(description: "") - - var responseRef: RequestCodableResponse, NoResponseCodable>? + let languageId: String = invalidLanguageId - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .decodeRequestDataResponseForSuccessCodable() - .sink { completion in - - expectation.fulfill() + let urlRequest: URLRequest = try getLanguageUrlRequest( + languageId: languageId + ) - } receiveValue: { (response: RequestCodableResponse, NoResponseCodable>) in + let response: RequestCodableResponse, NoResponseCodable> = try await requestSender.sendDataTask( + urlRequest: urlRequest, + urlSession: urlSession + ) + .decodeRequestDataResponseForSuccessCodable() - responseRef = response - } - .store(in: &cancellables) - - wait(for: [expectation], timeout: timeoutSeconds) - - XCTAssertNotNil(responseRef) - XCTAssertNil(responseRef?.successCodable) + #expect(response.successCodable == nil) } - func testSuccessfullyDecodeFailureCodableWhenRequestIsUnSuccessful() { - - let timeoutSeconds: TimeInterval = 15 - - let languageId: String = Self.invalidLanguageId + @Test() + func successfullyDecodeFailureCodableWhenRequestIsUnSuccessful() async throws { - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: languageId) - let requestSender = RequestSender() - let expectation = expectation(description: "") - - var responseRef: RequestCodableResponse, MobileContentApiErrorsCodable>? + let languageId: String = invalidLanguageId - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .decodeRequestDataResponseForSuccessOrFailureCodable() - .sink { completion in - - expectation.fulfill() - - } receiveValue: { (response: RequestCodableResponse, MobileContentApiErrorsCodable>) in + let urlRequest: URLRequest = try getLanguageUrlRequest( + languageId: languageId + ) - responseRef = response - } - .store(in: &cancellables) + let response: RequestCodableResponse, MobileContentApiErrorsCodable> = try await requestSender.sendDataTask( + urlRequest: urlRequest, + urlSession: urlSession + ) + .decodeRequestDataResponseForSuccessOrFailureCodable() - wait(for: [expectation], timeout: timeoutSeconds) - - XCTAssertNotNil(responseRef) - XCTAssertNotNil(responseRef?.failureCodable) + #expect(response.failureCodable != nil) } - func testReceiveDecodeErrorForSuccessCodableWhenRequestIsSuccessful() { - - let timeoutSeconds: TimeInterval = 15 - - let languageId: String = Self.englishLanguageId + @Test() + func receiveDecodeErrorForSuccessCodableWhenRequestIsSuccessful() async throws { - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: languageId) - let requestSender = RequestSender() - let expectation = expectation(description: "") - - var decodeErrorRef: Error? - var responseRef: RequestCodableResponse, NoResponseCodable>? + let languageId: String = englishLanguageId - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .decodeRequestDataResponseForSuccessCodable() - .sink { completion in - - switch completion { - case .finished: - break - - case .failure(let error): - decodeErrorRef = error - } - - expectation.fulfill() - - } receiveValue: { (response: RequestCodableResponse, NoResponseCodable>) in - - responseRef = response - } - .store(in: &cancellables) + let urlRequest: URLRequest = try getLanguageUrlRequest( + languageId: languageId + ) - wait(for: [expectation], timeout: timeoutSeconds) + let response: RequestCodableResponse, NoResponseCodable>? + let decodeError: Error? - XCTAssertNil(responseRef) - XCTAssertNil(responseRef?.successCodable) - XCTAssertNotNil(decodeErrorRef) + do { + + response = try await requestSender.sendDataTask( + urlRequest: urlRequest, + urlSession: urlSession + ) + .decodeRequestDataResponseForSuccessCodable() + + decodeError = nil + } + catch let error { + + response = nil + decodeError = error + } + + #expect(response == nil) + #expect(decodeError != nil) } - func testReceiveDecodeErrorForFailureCodableWhenRequestIsUnSuccessful() { + @Test() + func receiveDecodeErrorForFailureCodableWhenRequestIsUnSuccessful() async throws { - let timeoutSeconds: TimeInterval = 15 - - let languageId: String = Self.invalidLanguageId - - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: languageId) - let requestSender = RequestSender() - let expectation = expectation(description: "") - - var decodeErrorRef: Error? - var responseRef: RequestCodableResponse, MobileContentApiErrorCodable>? - - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) + let languageId: String = invalidLanguageId + + let urlRequest: URLRequest = try getLanguageUrlRequest( + languageId: languageId + ) + + let response: RequestCodableResponse, MobileContentApiErrorCodable>? + let decodeError: Error? + + do { + + response = try await requestSender.sendDataTask( + urlRequest: urlRequest, + urlSession: urlSession + ) .decodeRequestDataResponseForSuccessOrFailureCodable() - .sink { completion in - - switch completion { - case .finished: - break - - case .failure(let error): - decodeErrorRef = error - } - - expectation.fulfill() - - } receiveValue: { (response: RequestCodableResponse, MobileContentApiErrorCodable>) in - - responseRef = response - } - .store(in: &cancellables) - - wait(for: [expectation], timeout: timeoutSeconds) - - XCTAssertNil(responseRef) - XCTAssertNil(responseRef?.failureCodable) - XCTAssertNotNil(decodeErrorRef) + + decodeError = nil + } + catch let error { + + response = nil + decodeError = error + } + + #expect(response == nil) + #expect(decodeError != nil) } } diff --git a/Tests/RequestOperationTests/RequestSender/RequestSenderTests+URLError.swift b/Tests/RequestOperationTests/RequestSender/RequestSenderTests+URLError.swift index d52bf1b..1c300b6 100644 --- a/Tests/RequestOperationTests/RequestSender/RequestSenderTests+URLError.swift +++ b/Tests/RequestOperationTests/RequestSender/RequestSenderTests+URLError.swift @@ -6,18 +6,15 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation -import Combine +import Foundation extension RequestSenderTests { - func testSendUrlRequestFailedWithUrlError() { - - let timeoutSeconds: TimeInterval = 15 - - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - + @Test() + func sendDataTaskUrlErrorToError() async throws { + let requestBuilder = RequestBuilder() let urlWithBadHost: String = "https://37469355108471600907768582811183.com/languages/4" @@ -25,7 +22,7 @@ extension RequestSenderTests { let requestMethod: RequestMethod = .get let contentType: String = "application/vnd.api+json" - let urlRequest: URLRequest = requestBuilder.build( + let urlRequest: URLRequest = try requestBuilder.build( parameters: RequestBuilderParameters( urlSession: urlSession, urlString: urlString, @@ -38,33 +35,20 @@ extension RequestSenderTests { let requestSender = RequestSender() - let expectation = expectation(description: "") - - var errorRef: Error? + let errorRef: Error? - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .sink { completion in - - switch completion { - case .finished: - break - - case .failure(let error): - errorRef = error - } - - expectation.fulfill() - - } receiveValue: { (response: RequestDataResponse) in - - } - .store(in: &cancellables) + do { + _ = try await requestSender.sendDataTask(urlRequest: urlRequest, urlSession: urlSession) + errorRef = nil + } + catch let error { + errorRef = error + } - wait(for: [expectation], timeout: timeoutSeconds) + let error: Error = try #require(errorRef) - XCTAssertNotNil(errorRef) - XCTAssertNotNil(errorRef?.getUrlError()) - XCTAssertTrue(errorRef!.isUrlError) - XCTAssertTrue((errorRef as? NSError)?.domain == URLError.toErrorDomain) + #expect(error.getUrlError() != nil) + #expect(error.isUrlError == true) + #expect((error as NSError).domain == URLError.toErrorDomain) } } diff --git a/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Validate.swift b/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Validate.swift index e152f36..1a3042c 100644 --- a/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Validate.swift +++ b/Tests/RequestOperationTests/RequestSender/RequestSenderTests+Validate.swift @@ -6,102 +6,61 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation -import Combine +import Foundation extension RequestSenderTests { - func testValidateThrowsErrorWhenResponseHttpStatusCodeFallsOutsideOfValidateRange() { + @Test() + func validateFailsWithErrorWhenResponseHttpStatusCodeFallsOutsideOfValidateRange() async throws { + + let urlRequest: URLRequest = try getLanguageUrlRequest(languageId: invalidLanguageId) - let timeoutSeconds: TimeInterval = 15 - - let languageId: String = Self.invalidLanguageId - - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: languageId) - let requestSender = RequestSender() - let expectation = expectation(description: "") - let httpStatusCodeSuccessRange: Range = URLResponse.httpStatusCodeSuccessRange - var errorRef: Error? - - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .validate(successRange: httpStatusCodeSuccessRange) - .sink { completion in - - switch completion { - case .finished: - break - - case .failure(let error): - errorRef = error - } - - expectation.fulfill() - - } receiveValue: { (response: RequestDataResponse) in - - } - .store(in: &cancellables) - - wait(for: [expectation], timeout: timeoutSeconds) - - let responseRef: RequestDataResponse? = errorRef?.getRequestDataResponse() - - XCTAssertNotNil(errorRef) - XCTAssertNotNil(responseRef) - XCTAssertTrue(errorRef!.isRequestDataResponse) - XCTAssertTrue((errorRef as? NSError)?.domain == RequestDataResponse.toErrorDomain) - XCTAssertNotNil(responseRef?.urlResponse.httpStatusCode) - XCTAssertTrue(!httpStatusCodeSuccessRange.contains(responseRef!.urlResponse.httpStatusCode!)) + let errorRef: Error? + + do { + + _ = try await requestSender + .sendDataTask( + urlRequest: urlRequest, + urlSession: urlSession + ) + .validate(successRange: httpStatusCodeSuccessRange) + + errorRef = nil + } + catch let error { + errorRef = error + } + + let error: Error = try #require(errorRef) + let response: RequestDataResponse = try #require(error.getRequestDataResponse()) + let httpStatusCode: Int = try #require(response.urlResponse.httpStatusCode) + + #expect(error.isRequestDataResponse == true) + #expect((error as NSError).domain == RequestDataResponse.toErrorDomain) + #expect(!httpStatusCodeSuccessRange.contains(httpStatusCode)) } - func testValidateIsSuccessfulWhenResponseHttpStatusCodeFallsWithinValidateRange() { - - let timeoutSeconds: TimeInterval = 15 - - let languageId: String = Self.englishLanguageId + @Test() + func validateIsSuccessfulWhenResponseHttpStatusCodeFallsWithinValidateRange() async throws { - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: languageId) + let urlRequest: URLRequest = try getLanguageUrlRequest(languageId: englishLanguageId) let requestSender = RequestSender() - - let expectation = expectation(description: "") - + let httpStatusCodeSuccessRange: Range = URLResponse.httpStatusCodeSuccessRange - var responseRef: RequestDataResponse? - - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) + let response = try await requestSender.sendDataTask(urlRequest: urlRequest, urlSession: urlSession) .validate(successRange: httpStatusCodeSuccessRange) - .sink { completion in - - switch completion { - case .finished: - break - - case .failure( _): - break - } - - expectation.fulfill() - - } receiveValue: { (response: RequestDataResponse) in - - responseRef = response - } - .store(in: &cancellables) - wait(for: [expectation], timeout: timeoutSeconds) + let httpStatusCode: Int = try #require(response.urlResponse.httpStatusCode) - XCTAssertNotNil(responseRef?.urlResponse.httpStatusCode) - XCTAssertTrue(httpStatusCodeSuccessRange.contains(responseRef!.urlResponse.httpStatusCode!)) + #expect(httpStatusCodeSuccessRange.contains(httpStatusCode)) } } diff --git a/Tests/RequestOperationTests/RequestSender/RequestSenderTests.swift b/Tests/RequestOperationTests/RequestSender/RequestSenderTests.swift index a399ba2..bede678 100644 --- a/Tests/RequestOperationTests/RequestSender/RequestSenderTests.swift +++ b/Tests/RequestOperationTests/RequestSender/RequestSenderTests.swift @@ -6,99 +6,73 @@ // Copyright © 2024 Cru. All rights reserved. // -import XCTest +import Testing @testable import RequestOperation -import Combine +import Foundation -class RequestSenderTests: XCTestCase { - - static let languagesUrl: String = "https://mobile-content-api-stage.cru.org/languages" - static let englishLanguageId: String = "4" - static let invalidLanguageId: String = "-37469355108471600907768582811183" +struct RequestSenderTests { - var cancellables: Set = Set() + let urlSession: URLSession = CreateUrlSession().createIgnoreCacheSession(timeoutIntervalForRequest: 10) + let languagesUrl: String = "https://mobile-content-api-stage.cru.org/languages" + let englishLanguageId: String = "4" + let invalidLanguageId: String = "32984724" - static func buildGetLanguageUrlRequest(urlSession: URLSession, languageId: String) -> URLRequest { + @Test + func sendDataIsSuccessfulHttpStatusCode() async throws { - let requestBuilder = RequestBuilder() - - let urlString: String = Self.languagesUrl + "/" + languageId - let requestMethod: RequestMethod = .get - let contentType: String = "application/vnd.api+json" + let requestSender = RequestSender() - let urlRequest: URLRequest = requestBuilder.build( - parameters: RequestBuilderParameters( - urlSession: urlSession, - urlString: urlString, - method: requestMethod, - headers: ["Content-Type": contentType], - httpBody: nil, - queryItems: nil - ) + let urlRequest: URLRequest = try getLanguageUrlRequest( + languageId: englishLanguageId ) - return urlRequest + let response = try await requestSender.sendDataTask(urlRequest: urlRequest, urlSession: urlSession) + + #expect(response.urlResponse.isSuccessHttpStatusCode == true) } - func testSendUrlRequestIsSuccessful() { - - let timeoutSeconds: TimeInterval = 15 + @Test + func sendDataIsUnSuccessfulHttpStatusCode() async throws { - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: Self.englishLanguageId) - let requestSender = RequestSender() - let expectation = expectation(description: "") - - var responseRef: RequestDataResponse? - - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .sink { completion in - - expectation.fulfill() - - } receiveValue: { (response: RequestDataResponse) in - - responseRef = response - } - .store(in: &cancellables) + let urlRequest: URLRequest = try getLanguageUrlRequest( + languageId: invalidLanguageId + ) - wait(for: [expectation], timeout: timeoutSeconds) + let response = try await requestSender.sendDataTask(urlRequest: urlRequest, urlSession: urlSession) - XCTAssertNotNil(responseRef) - XCTAssertTrue(responseRef!.urlResponse.isSuccessHttpStatusCode) + #expect(response.urlResponse.isSuccessHttpStatusCode == false) } +} + +extension RequestSenderTests { - func testSendUrlRequestIsUnSuccessful() { - - let timeoutSeconds: TimeInterval = 15 - - let urlSession: URLSession = RequestUrlSession.createIgnoreCacheSession(timeoutIntervalForRequest: timeoutSeconds) - - let urlRequest: URLRequest = Self.buildGetLanguageUrlRequest(urlSession: urlSession, languageId: Self.invalidLanguageId) - - let requestSender = RequestSender() - - let expectation = expectation(description: "") + func getLanguageUrlRequest(languageId: String) throws -> URLRequest { - var responseRef: RequestDataResponse? + let urlString: String = languagesUrl + "/" + languageId - requestSender.sendDataTaskPublisher(urlRequest: urlRequest, urlSession: urlSession) - .sink { completion in - - expectation.fulfill() - - } receiveValue: { (response: RequestDataResponse) in + return try getLanguageUrlRequest(urlString: urlString) + } + + func getLanguageUrlRequest(urlString: String) throws -> URLRequest { - responseRef = response - } - .store(in: &cancellables) + let requestBuilder = RequestBuilder() - wait(for: [expectation], timeout: timeoutSeconds) + let requestMethod: RequestMethod = .get + let contentType: String = "application/vnd.api+json" + + let urlRequest: URLRequest = try requestBuilder.build( + parameters: RequestBuilderParameters( + urlSession: urlSession, + urlString: urlString, + method: requestMethod, + headers: ["Content-Type": contentType], + httpBody: nil, + queryItems: nil + ) + ) - XCTAssertNotNil(responseRef) - XCTAssertFalse(responseRef!.urlResponse.isSuccessHttpStatusCode) + return urlRequest } } diff --git a/Tests/RequestOperationTests/UrlSession/Config/CreateIgnoreCacheSessionConfigTests.swift b/Tests/RequestOperationTests/UrlSession/Config/CreateIgnoreCacheSessionConfigTests.swift index 575b3de..94ef36f 100644 --- a/Tests/RequestOperationTests/UrlSession/Config/CreateIgnoreCacheSessionConfigTests.swift +++ b/Tests/RequestOperationTests/UrlSession/Config/CreateIgnoreCacheSessionConfigTests.swift @@ -5,23 +5,25 @@ // Created by Levi Eggert on 5/30/25. // -import XCTest +import Testing @testable import RequestOperation +import Foundation -class CreateIgnoreCacheSessionConfigTests: XCTestCase { +struct CreateIgnoreCacheSessionConfigTests { - func testCreateIgnoreCacheSessionIgnoresTheCache() { + @Test + func createIgnoreCacheSessionIgnoresTheCache() { let ignoreCacheSesion: URLSession = URLSession(configuration: CreateIgnoreCacheSessionConfig().createConfig()) - XCTAssertTrue(ignoreCacheSesion.configuration.requestCachePolicy == .reloadIgnoringLocalCacheData, "requestCachePolicy should equal reloadIgnoringLocalCacheData") - XCTAssertNil(ignoreCacheSesion.configuration.urlCache, "urlCache should be nil") - - XCTAssertTrue(ignoreCacheSesion.configuration.httpShouldSetCookies == false, "httpShouldSetCookies should be false") - XCTAssertNil(ignoreCacheSesion.configuration.httpCookieStorage, "httpCookieStorage should be nil") + #expect(ignoreCacheSesion.configuration.requestCachePolicy == .reloadIgnoringLocalCacheData) + #expect(ignoreCacheSesion.configuration.urlCache == nil) + #expect(ignoreCacheSesion.configuration.httpShouldSetCookies == false) + #expect(ignoreCacheSesion.configuration.httpCookieStorage == nil) } - func testCreateIgnoreCacheSessionTimeoutIsCorrect() { + @Test + func createIgnoreCacheSessionTimeoutIsCorrect() { let timeoutInterval: TimeInterval = 12 @@ -29,6 +31,6 @@ class CreateIgnoreCacheSessionConfigTests: XCTestCase { configuration: CreateIgnoreCacheSessionConfig().createConfig(timeoutIntervalForRequest: timeoutInterval) ) - XCTAssertTrue(ignoreCacheSesion.configuration.timeoutIntervalForRequest == timeoutInterval) + #expect(ignoreCacheSesion.configuration.timeoutIntervalForRequest == timeoutInterval) } }