From 153feea30da5fe1584a03fca0735ec3ee87b7067 Mon Sep 17 00:00:00 2001 From: leogdion Date: Thu, 11 May 2023 09:03:31 -0400 Subject: [PATCH] Adding API to Service and Requests(#20) --- .github/workflows/Prch.yml | 227 ++++++---- .gitignore | 37 +- .periphery.yml | 4 +- .swift-version | 2 +- .swiftformat | 2 +- .swiftlint.yml | 25 +- LICENSE | 2 +- Mintfile | 3 +- Package.resolved | 133 ------ Package.swift | 67 +-- README.md | 4 +- Scripts/docc.sh | 3 + Scripts/gh-md-toc | 411 ++++++++++++++++++ Scripts/lint.sh | 45 ++ Sources/Prch/API.swift | 12 - .../Prch/AnyCodable/AnyCodable.Codable.swift | 105 ----- .../AnyCodable/AnyCodable.Equatable.swift | 62 --- .../AnyCodable/AnyCodable.Expressible.swift | 41 -- .../AnyCodable.StringConverable.swift | 27 -- Sources/Prch/AnyCodable/AnyCodable.swift | 12 - Sources/Prch/AuthorizationManager.swift | 4 + Sources/Prch/BasicResponse.swift | 65 --- Sources/Prch/BodyRequest.swift | 14 - Sources/Prch/Client.swift | 96 ---- Sources/Prch/ClientError.swift | 47 -- Sources/Prch/ClientResult.swift | 72 --- Sources/Prch/Coding/Coding.swift | 55 --- Sources/Prch/Coding/DateFormatter.swift | 34 -- Sources/Prch/Coding/DecodingError.swift | 17 - .../Prch/Coding/KeyedDecodingContainer.swift | 125 ------ .../Prch/Coding/KeyedEncodingContainer.swift | 13 - Sources/Prch/Coding/RequestEncoder.swift | 7 - Sources/Prch/Coding/ResponseDecoder.swift | 16 - Sources/Prch/Coding/StringCodingKey.swift | 19 - Sources/Prch/DateEncodingContainer.swift | 19 - .../Prch/Deprecated/DeprecatedRequest.swift | 59 --- .../Prch/Deprecated/DeprecatedResponse.swift | 8 - .../Deprecated/DeprecatedResponseResult.swift | 30 -- Sources/Prch/Deprecated/TypeAliases.swift | 19 - Sources/Prch/Model.swift | 15 - Sources/Prch/Models/DateDay.swift | 82 ---- Sources/Prch/Models/File.swift | 3 - Sources/Prch/Models/ID.swift | 3 - Sources/Prch/Models/UploadFile.swift | 29 -- .../Prch/Networking/ResponseComponents.swift | 6 - Sources/Prch/Networking/Session.swift | 27 -- Sources/Prch/Networking/Task.swift | 9 - Sources/Prch/Networking/URL.swift | 11 - Sources/Prch/Networking/URLSession.swift | 96 ---- .../Prch/Networking/URLSessionResponse.swift | 37 -- Sources/Prch/NullAuthorizationManager.swift | 9 + Sources/Prch/Request.swift | 32 -- Sources/Prch/Response.swift | 11 - Sources/Prch/ResponseResult.swift | 32 -- Sources/Prch/Security/Authentication.swift | 4 - .../Prch/Security/BasicAuthentication.swift | 13 - .../Prch/Security/SecurityRequirement.swift | 9 - Sources/Prch/Service.swift | 64 +-- Sources/Prch/ServiceProtocol.swift | 2 + Sources/Prch/ServiceRequest.swift | 31 -- Sources/Prch/Session/LegacyCoder.swift | 5 - Sources/Prch/Session/Session.swift | 31 +- ...ation.swift => SessionAuthorization.swift} | 2 +- .../Prch/Session/URLSession/URLSession.swift | 21 +- .../URLSession/URLSessionResponse.swift | 12 +- Sources/Prch/StatusCodeProvider.swift | 5 - Sources/Prch/SuccessModel.swift | 3 - Sources/PrchModel/API.swift | 10 + Sources/PrchModel/Array.swift | 8 +- Sources/PrchModel/Content.swift | 49 --- Sources/PrchModel/ContentDecodable.swift | 2 +- Sources/PrchModel/CustomServiceDecoding.swift | 5 + Sources/PrchModel/CustomServiceEncoding.swift | 5 + Sources/PrchModel/Decodable.swift | 2 +- .../PrchModel/{Coder.swift => Decoder.swift} | 6 +- Sources/PrchModel/Empty.swift | 2 +- Sources/PrchModel/Encoder.swift | 7 + Sources/PrchModel/JSONCoder.swift | 68 --- Sources/PrchModel/JSONDecoder.swift | 5 + Sources/PrchModel/JSONEncoder.swift | 5 + Sources/PrchModel/RequestMethod.swift | 2 +- Sources/PrchModel/ServiceCall.swift | 40 +- Tests/LinuxMain.swift | 8 - Tests/PrchTests/PrchTests.swift | 10 +- Tests/PrchTests/ServiceImplTests.swift | 84 +++- Tests/PrchTests/XCTestManifests.swift | 18 - 86 files changed, 910 insertions(+), 1973 deletions(-) delete mode 100644 Package.resolved create mode 100755 Scripts/docc.sh create mode 100755 Scripts/gh-md-toc create mode 100755 Scripts/lint.sh delete mode 100644 Sources/Prch/API.swift delete mode 100644 Sources/Prch/AnyCodable/AnyCodable.Codable.swift delete mode 100644 Sources/Prch/AnyCodable/AnyCodable.Equatable.swift delete mode 100644 Sources/Prch/AnyCodable/AnyCodable.Expressible.swift delete mode 100644 Sources/Prch/AnyCodable/AnyCodable.StringConverable.swift delete mode 100644 Sources/Prch/AnyCodable/AnyCodable.swift create mode 100644 Sources/Prch/AuthorizationManager.swift delete mode 100644 Sources/Prch/BasicResponse.swift delete mode 100644 Sources/Prch/BodyRequest.swift delete mode 100644 Sources/Prch/Client.swift delete mode 100644 Sources/Prch/ClientError.swift delete mode 100644 Sources/Prch/ClientResult.swift delete mode 100644 Sources/Prch/Coding/Coding.swift delete mode 100644 Sources/Prch/Coding/DateFormatter.swift delete mode 100644 Sources/Prch/Coding/DecodingError.swift delete mode 100644 Sources/Prch/Coding/KeyedDecodingContainer.swift delete mode 100644 Sources/Prch/Coding/KeyedEncodingContainer.swift delete mode 100644 Sources/Prch/Coding/RequestEncoder.swift delete mode 100644 Sources/Prch/Coding/ResponseDecoder.swift delete mode 100644 Sources/Prch/Coding/StringCodingKey.swift delete mode 100644 Sources/Prch/DateEncodingContainer.swift delete mode 100644 Sources/Prch/Deprecated/DeprecatedRequest.swift delete mode 100644 Sources/Prch/Deprecated/DeprecatedResponse.swift delete mode 100644 Sources/Prch/Deprecated/DeprecatedResponseResult.swift delete mode 100644 Sources/Prch/Deprecated/TypeAliases.swift delete mode 100644 Sources/Prch/Model.swift delete mode 100644 Sources/Prch/Models/DateDay.swift delete mode 100644 Sources/Prch/Models/File.swift delete mode 100644 Sources/Prch/Models/ID.swift delete mode 100644 Sources/Prch/Models/UploadFile.swift delete mode 100644 Sources/Prch/Networking/ResponseComponents.swift delete mode 100644 Sources/Prch/Networking/Session.swift delete mode 100644 Sources/Prch/Networking/Task.swift delete mode 100644 Sources/Prch/Networking/URL.swift delete mode 100644 Sources/Prch/Networking/URLSession.swift delete mode 100644 Sources/Prch/Networking/URLSessionResponse.swift create mode 100644 Sources/Prch/NullAuthorizationManager.swift delete mode 100644 Sources/Prch/Request.swift delete mode 100644 Sources/Prch/Response.swift delete mode 100644 Sources/Prch/ResponseResult.swift delete mode 100644 Sources/Prch/Security/Authentication.swift delete mode 100644 Sources/Prch/Security/BasicAuthentication.swift delete mode 100644 Sources/Prch/Security/SecurityRequirement.swift delete mode 100644 Sources/Prch/ServiceRequest.swift delete mode 100644 Sources/Prch/Session/LegacyCoder.swift rename Sources/Prch/Session/{URLSession/URLSessionAuthorization.swift => SessionAuthorization.swift} (60%) delete mode 100644 Sources/Prch/StatusCodeProvider.swift delete mode 100644 Sources/Prch/SuccessModel.swift create mode 100644 Sources/PrchModel/API.swift create mode 100644 Sources/PrchModel/CustomServiceDecoding.swift create mode 100644 Sources/PrchModel/CustomServiceEncoding.swift rename Sources/PrchModel/{Coder.swift => Decoder.swift} (72%) create mode 100644 Sources/PrchModel/Encoder.swift delete mode 100644 Sources/PrchModel/JSONCoder.swift create mode 100644 Sources/PrchModel/JSONDecoder.swift create mode 100644 Sources/PrchModel/JSONEncoder.swift delete mode 100644 Tests/LinuxMain.swift delete mode 100644 Tests/PrchTests/XCTestManifests.swift diff --git a/.github/workflows/Prch.yml b/.github/workflows/Prch.yml index 09d8636..7723315 100644 --- a/.github/workflows/Prch.yml +++ b/.github/workflows/Prch.yml @@ -1,119 +1,198 @@ name: Prch on: push: - branches: - - '*' - - 'feature/*' - - 'release/*' - - 'bugfix/*' - tags: '*' + branches-ignore: + - '*WIP' + +env: + CODEQL_ENABLE_EXPERIMENTAL_FEATURES_SWIFT: true + jobs: build-ubuntu: name: Build on Ubuntu env: - PACKAGE_NAME: Prch SWIFT_VER: ${{ matrix.swift-version }} runs-on: ${{ matrix.runs-on }} if: "!contains(github.event.head_commit.message, 'ci skip')" strategy: matrix: - runs-on: [ubuntu-18.04, ubuntu-20.04] - swift-version: [5.2.4, 5.3.3, 5.4.1, 5.5.2, 5.6.2] + runs-on: [ubuntu-20.04, ubuntu-22.04] + swift-version: [5.7.3, 5.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set Ubuntu Release DOT run: echo "RELEASE_DOT=$(lsb_release -sr)" >> $GITHUB_ENV - name: Set Ubuntu Release NUM run: echo "RELEASE_NUM=${RELEASE_DOT//[-._]/}" >> $GITHUB_ENV - name: Set Ubuntu Codename run: echo "RELEASE_NAME=$(lsb_release -sc)" >> $GITHUB_ENV + - name: Cache swift package modules + id: cache-spm-linux + uses: actions/cache@v3 + env: + cache-name: cache-spm + with: + path: .build + key: ${{ runner.os }}-${{ env.RELEASE_DOT }}-${{ env.cache-name }}-${{ matrix.swift-version }}-${{ hashFiles('Package.resolved') }} + restore-keys: | + ${{ runner.os }}-${{ env.RELEASE_DOT }}-${{ env.cache-name }}-${{ matrix.swift-version }}- + ${{ runner.os }}-${{ env.RELEASE_DOT }}-${{ env.cache-name }}- + - name: Cache swift + id: cache-swift-linux + uses: actions/cache@v3 + env: + cache-name: cache-swift + with: + path: swift-${{ env.SWIFT_VER }}-RELEASE-ubuntu${{ env.RELEASE_DOT }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.swift-version }}-${{ env.RELEASE_DOT }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}-${{ matrix.swift-version }}- - name: Download Swift + if: steps.cache-swift-linux.outputs.cache-hit != 'true' run: curl -O https://download.swift.org/swift-${SWIFT_VER}-release/ubuntu${RELEASE_NUM}/swift-${SWIFT_VER}-RELEASE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz - name: Extract Swift + if: steps.cache-swift-linux.outputs.cache-hit != 'true' run: tar xzf swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}.tar.gz - name: Add Path run: echo "$GITHUB_WORKSPACE/swift-${SWIFT_VER}-RELEASE-ubuntu${RELEASE_DOT}/usr/bin" >> $GITHUB_PATH - name: Build run: swift build - - name: Run tests - run: swift test --enable-test-discovery --enable-code-coverage - # - name: Prepare Code Coverage - # run: llvm-cov export -format="lcov" .build/x86_64-unknown-linux-gnu/debug/${{ env.PACKAGE_NAME }}PackageTests.xctest -instr-profile .build/debug/codecov/default.profdata > info.lcov - # - name: Upload to CodeCov.io - # run: bash <(curl https://codecov.io/bash) -F github -F ${RELEASE_NAME} -F ${SWIFT_VER} - # env: - # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Test + run: swift test --enable-code-coverage + - uses: sersoft-gmbh/swift-coverage-action@v3 + with: + fail-on-empty-output: true + - name: Upload package coverage to Codecov + uses: codecov/codecov-action@v3 + with: + fail_ci_if_error: true + flags: spm,swift-${{ matrix.swift-version }} + token: ${{ secrets.CODECOV_TOKEN }} build-macos: name: Build on macOS - env: - PACKAGE_NAME: Prch runs-on: ${{ matrix.runs-on }} if: "!contains(github.event.head_commit.message, 'ci skip')" strategy: matrix: - runs-on: [macos-11, macos-10.15] - xcode: ["/Applications/Xcode_12.4.app", "/Applications/Xcode_11.7.app"] include: - - runs-on: macos-10.15 - xcode: "/Applications/Xcode_11.4.1.app" - - runs-on: macos-10.15 - xcode: "/Applications/Xcode_11.5.app" - - runs-on: macos-10.15 - xcode: "/Applications/Xcode_11.6.app" - - runs-on: macos-11 - xcode: "/Applications/Xcode_12.5.1.app" - - runs-on: macos-11 - xcode: "/Applications/Xcode_13.0.app" - - runs-on: macos-11 - xcode: "/Applications/Xcode_13.1.app" - - runs-on: macos-11 - xcode: "/Applications/Xcode_13.2.1.app" - - runs-on: macos-12 - xcode: "/Applications/Xcode_13.2.1.app" - runs-on: macos-12 - xcode: "/Applications/Xcode_13.3.1.app" + xcode: "/Applications/Xcode_14.0.1.app" + iOSVersion: "16.0" + watchOSVersion: "9.0" + watchName: "Apple Watch Series 8 (41mm)" + iPhoneName: "iPhone 12" - runs-on: macos-12 - xcode: "/Applications/Xcode_13.4.app" + xcode: "/Applications/Xcode_14.1.app" + iOSVersion: "16.1" + watchOSVersion: "9.1" + watchName: "Apple Watch Ultra (49mm)" + iPhoneName: "iPhone 13" - runs-on: macos-12 - xcode: "/Applications/Xcode_13.4.1.app" + xcode: "/Applications/Xcode_14.2.app" + iOSVersion: "16.2" + watchOSVersion: "9.1" + watchName: "Apple Watch Ultra (49mm)" + iPhoneName: "iPhone 14" + - runs-on: macos-13 + xcode: "/Applications/Xcode_14.3.app" + iOSVersion: "16.4" + watchOSVersion: "9.4" + watchName: "Apple Watch Ultra (49mm)" + iPhoneName: "iPhone 14 Pro Max" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - name: Cache swift package modules + id: cache-spm-macos + uses: actions/cache@v3 + env: + cache-name: cache-spm + with: + path: .build + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.xcode }}-${{ hashFiles('Package.resolved') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.xcode }}- + - name: Cache mint + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + id: cache-mint + uses: actions/cache@v3 + env: + cache-name: cache-mint + with: + path: .mint + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Mintfile') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- - name: Set Xcode Name run: echo "XCODE_NAME=$(basename -- ${{ matrix.xcode }} | sed 's/\.[^.]*$//' | cut -d'_' -f2)" >> $GITHUB_ENV - name: Setup Xcode run: sudo xcode-select -s ${{ matrix.xcode }}/Contents/Developer + - name: Install mint + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + run: | + brew update + brew install mint + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} - name: Build run: swift build - - name: Lint - if: startsWith(github.ref, 'refs/tags/') != true - run: swift run swiftformat --lint . && swift run swiftlint - - name: Run tests + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + - name: Run Swift Package tests run: swift test -v --enable-code-coverage - # - name: Prepare Code Coverage - # run: xcrun llvm-cov export -format="lcov" .build/debug/${{ env.PACKAGE_NAME }}PackageTests.xctest/Contents/MacOS/${{ env.PACKAGE_NAME }}PackageTests -instr-profile .build/debug/codecov/default.profdata > info.lcov - # - name: Upload to CodeCov.io - # run: bash <(curl https://codecov.io/bash) -F github -F macOS -F ${{ matrix.runs-on }} - # env: - # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Build sourcedocs Documentation - if: ${{ matrix.os == 'macos-11' && matrix.xcode == '/Applications/Xcode_12.5.app' && !startsWith(github.ref, 'refs/tags/') }} - run: "swift run sourcedocs generate build -cra\ngit config --local user.email \"action@github.com\"\ngit config --local user.name \"GitHub Action\"\ngit status\ngit add Documentation\ngit diff-index --quiet HEAD || git commit -m \"[github action] Update Docs\"\ngit push \n" - deploy: - name: Deploy to Netlify - needs: [build-macos, build-ubuntu] - env: - PACKAGE_NAME: Prch - runs-on: macos-11 - if: ${{ github.ref == 'refs/heads/main' }} - steps: - - uses: actions/checkout@v2 - - name: Setup Netlify - run: brew install netlify-cli - - name: Setup Xcode - run: sudo xcode-select -s /Applications/Xcode_13.0.app/Contents/Developer - - name: Resolve Package Dependencies - run: xcodebuild -resolvePackageDependencies -scheme ${{ env.PACKAGE_NAME }} -derivedDataPath DerivedData - - name: Build DocC Documentation - run: xcodebuild docbuild -scheme ${{ env.PACKAGE_NAME }} -destination 'platform=macOS' -derivedDataPath DerivedData - # - name: Deploy Files - # run: netlify deploy --site ${{ secrets.NETLIFY_SITE_ID }} --auth ${{ secrets.NETLIFY_AUTH_TOKEN }} --prod + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + - uses: sersoft-gmbh/swift-coverage-action@v2 + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + - name: Upload SPM coverage to Codecov + uses: codecov/codecov-action@v2 + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + with: + fail_ci_if_error: true + flags: spm + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + - name: Lint + run: ./scripts/lint.sh + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + - name: Dump PIF + #if: startsWith(matrix.xcode,'/Applications/Xcode_14') + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + run: | + swift package dump-pif > /dev/null + MAX_ATTEMPT=3 + ATTEMPT=0 + while [ -z $SUCCESS ] && [ "$ATTEMPT" -le "$MAX_ATTEMPT" ]; do + xcodebuild clean -scheme Prch -destination 'generic/platform=iOS' | grep -q "CLEAN SUCCEEDED" && SUCCESS=true + ATTEMPT=$(($ATTEMPT+1)) + done + - name: Run iOS target tests + run: xcodebuild test -scheme Prch-Package -sdk iphonesimulator -destination 'platform=iOS Simulator,name=${{ matrix.iPhoneName }},OS=${{ matrix.iOSVersion }}' -enableCodeCoverage YES build test + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + - uses: sersoft-gmbh/swift-coverage-action@v3 + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + with: + fail-on-empty-output: true + - name: Upload iOS coverage to Codecov + uses: codecov/codecov-action@v3 + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + with: + fail_ci_if_error: true + flags: iOS,iOS-${{ matrix.iOSVersion }} + token: ${{ secrets.CODECOV_TOKEN }} + - name: Run watchOS target tests + run: xcodebuild build -scheme Prch-Package -sdk watchsimulator -destination 'platform=watchOS Simulator,name=${{ matrix.watchName }},OS=${{ matrix.watchOSVersion }}' + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + - uses: sersoft-gmbh/swift-coverage-action@v3 + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + with: + fail-on-empty-output: true + - name: Upload watchOS coverage to Codecov + if: startsWith(matrix.xcode,'/Applications/Xcode_14.3') + uses: codecov/codecov-action@v3 + with: + fail_ci_if_error: true + flags: watchOS,watchOS${{ matrix.watchOSVersion }} + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 8f5bea4..ddf9c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -61,8 +61,6 @@ xcuserdata/ build/ DerivedData/ *.moved-aside - -# Xcode *.pbxuser !default.pbxuser *.mode1v3 @@ -85,12 +83,11 @@ timeline.xctimeline playground.xcworkspace # Swift Package Manager -# # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved -*.xcodeproj +# *.xcodeproj # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project .swiftpm @@ -111,9 +108,9 @@ playground.xcworkspace Carthage/Build/ -# Accio dependency management -Dependencies/ -.accio/ +# Add this lines if you are using Accio dependency management (Deprecated since Xcode 12) +# Dependencies/ +# .accio/ # fastlane # It is recommended to not store the screenshots in the git repo. @@ -133,16 +130,21 @@ fastlane/test_output iOSInjectionProject/ ### SwiftPackageManager ### -Packages +#Packages xcuserdata *.xcodeproj ### SwiftPM ### + + ### Xcode ### # Xcode # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + ## Gcc Patch /*.gcno @@ -153,7 +155,20 @@ xcuserdata !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings -*.pxd/QuickLook -# End of https://www.toptal.com/developers/gitignore/api/macos,swift,SwiftPackageManager,Xcode +/Support/**/*.plist +/Support/**/*.entitlements +*.ipa +*.zip +*.pkg +*.app + +**/*.xcassets/**/*.png +**/*.xcassets/**/*.pdf +**/*.xcassets/**/*.svg + +.bundle +vendor +.mint +# End of https://www.toptal.com/developers/gitignore/api/xcode,macos -*.pxd/QuickLook +*.apns \ No newline at end of file diff --git a/.periphery.yml b/.periphery.yml index c5ec0cd..0967ef4 100644 --- a/.periphery.yml +++ b/.periphery.yml @@ -1,3 +1 @@ -targets: -- FloxBxModeling -- FloxBxNetworking +{} diff --git a/.swift-version b/.swift-version index 7ed6ff8..760606e 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5 +5.7 diff --git a/.swiftformat b/.swiftformat index 7001c5e..2601bf7 100644 --- a/.swiftformat +++ b/.swiftformat @@ -2,6 +2,6 @@ --header strip --commas inline --disable wrapMultilineStatementBraces ---extensionacl on-extension +--extensionacl on-declarations --decimalgrouping 3,4 --exclude .build, DerivedData diff --git a/.swiftlint.yml b/.swiftlint.yml index 808ca6a..8980ca3 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -30,10 +30,9 @@ opt_in_rules: - file_name - file_name_no_space - file_types_order - - first_where - flatmap_over_map_reduce - force_unwrapping - - function_default_parameter_at_end + #- function_default_parameter_at_end - ibinspectable_in_extension - identical_operands - implicit_return @@ -45,9 +44,9 @@ opt_in_rules: - legacy_random - literal_expression_end_indentation - lower_acl_than_parent - - missing_docs + #- missing_docs - modifier_order - - multiline_arguments + #- multiline_arguments - multiline_arguments_brackets - multiline_function_chains - multiline_literal_brackets @@ -83,12 +82,11 @@ opt_in_rules: - strong_iboutlet - switch_case_on_newline - toggle_bool - - trailing_closure + #- trailing_closure - type_contents_order - unavailable_function - unneeded_parentheses_in_closure_argument - unowned_variable_capture - - untyped_error_in_catch - unused_declaration - unused_import - vertical_parameter_alignment_on_call @@ -97,15 +95,20 @@ opt_in_rules: - vertical_whitespace_opening_braces - xct_specific_matcher - yoda_condition +generic_type_name: + max_length: 48 cyclomatic_complexity: - 6 - 9 +type_body_length: + - 250 + - 500 file_length: - - 200 - - 200 + - 400 + - 800 function_body_length: - - 15 - 25 + - 40 function_parameter_count: 8 line_length: - 90 @@ -113,10 +116,10 @@ line_length: identifier_name: excluded: - id + - db excluded: - - Tests/*/XCTestManifests.swift + - Tests - DerivedData - .build - - Tests/LinuxMain.swift indentation_width: indentation_width: 2 diff --git a/LICENSE b/LICENSE index c1a4949..b29299c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 BrightDigit +Copyright (c) 2023 BrightDigit Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Mintfile b/Mintfile index 5e01529..6cb5f86 100644 --- a/Mintfile +++ b/Mintfile @@ -1,3 +1,2 @@ nicklockwood/SwiftFormat@0.47.0 -realm/SwiftLint@0.41.0 -peripheryapp/periphery@2.10.0 \ No newline at end of file +realm/SwiftLint@0.41.0 \ No newline at end of file diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 34e5666..0000000 --- a/Package.resolved +++ /dev/null @@ -1,133 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Komondor", - "repositoryURL": "https://github.com/shibapm/Komondor", - "state": { - "branch": null, - "revision": "90b087b1e39069684b1ff4bf915c2aae594f2d60", - "version": "1.1.3" - } - }, - { - "package": "Logger", - "repositoryURL": "https://github.com/shibapm/Logger", - "state": { - "branch": null, - "revision": "53c3ecca5abe8cf46697e33901ee774236d94cce", - "version": "0.2.3" - } - }, - { - "package": "PackageConfig", - "repositoryURL": "https://github.com/shibapm/PackageConfig.git", - "state": { - "branch": null, - "revision": "58523193c26fb821ed1720dcd8a21009055c7cdb", - "version": "1.1.3" - } - }, - { - "package": "Rocket", - "repositoryURL": "https://github.com/shibapm/Rocket.git", - "state": { - "branch": null, - "revision": "9880a5beb7fcb9e61ddd5764edc1700b8c418deb", - "version": "1.2.1" - } - }, - { - "package": "ShellOut", - "repositoryURL": "https://github.com/JohnSundell/ShellOut.git", - "state": { - "branch": null, - "revision": "e1577acf2b6e90086d01a6d5e2b8efdaae033568", - "version": "2.3.0" - } - }, - { - "package": "SourceKitten", - "repositoryURL": "https://github.com/jpsim/SourceKitten.git", - "state": { - "branch": null, - "revision": "7f4be006fe73211b0fd9666c73dc2f2303ffa756", - "version": "0.31.0" - } - }, - { - "package": "swift-argument-parser", - "repositoryURL": "https://github.com/apple/swift-argument-parser", - "state": { - "branch": null, - "revision": "9564d61b08a5335ae0a36f789a7d71493eacadfc", - "version": "0.3.2" - } - }, - { - "package": "swift-test-codecov", - "repositoryURL": "https://github.com/brightdigit/swift-test-codecov", - "state": { - "branch": null, - "revision": "8ca50588a72d1869cc6edafdee5f1e7fa9783b7b", - "version": "1.0.0" - } - }, - { - "package": "SwiftFormat", - "repositoryURL": "https://github.com/nicklockwood/SwiftFormat", - "state": { - "branch": null, - "revision": "f14f4f717e7e1d275acd7557d64c94cfef5723e6", - "version": "0.49.4" - } - }, - { - "package": "SwiftLint", - "repositoryURL": "https://github.com/realm/SwiftLint", - "state": { - "branch": null, - "revision": "e820e750b08bd67bc9d98f4817868e9bc3d5d865", - "version": "0.44.0" - } - }, - { - "package": "SwiftShell", - "repositoryURL": "https://github.com/kareman/SwiftShell", - "state": { - "branch": null, - "revision": "a6014fe94c3dbff0ad500e8da4f251a5d336530b", - "version": "5.1.0-beta.1" - } - }, - { - "package": "SwiftyTextTable", - "repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git", - "state": { - "branch": null, - "revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", - "version": "0.9.0" - } - }, - { - "package": "SWXMLHash", - "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", - "state": { - "branch": null, - "revision": "9183170d20857753d4f331b0ca63f73c60764bf3", - "version": "5.0.2" - } - }, - { - "package": "Yams", - "repositoryURL": "https://github.com/jpsim/Yams", - "state": { - "branch": null, - "revision": "9ff1cc9327586db4e0c8f46f064b6a82ec1566fa", - "version": "4.0.6" - } - } - ] - }, - "version": 1 -} diff --git a/Package.swift b/Package.swift index 489b698..bebeba9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,64 +1,27 @@ -// swift-tools-version:5.2.0 -// swiftlint:disable explicit_top_level_acl line_length +// swift-tools-version:5.7 +// swiftlint:disable explicit_top_level_acl explicit_acl + import PackageDescription let package = Package( name: "Prch", - platforms: [ - .macOS(.v10_15), - .iOS(.v10), - .tvOS(.v10), - .watchOS(.v3) - ], + platforms: [.macOS(.v12), .iOS(.v14), .watchOS(.v7)], products: [ - .library(name: "Prch", targets: ["Prch"]) + .library( + name: "Prch", + targets: ["Prch"] + ), + .library( + name: "PrchModel", + targets: ["PrchModel"] + ) ], dependencies: [ - .package(url: "https://github.com/shibapm/Komondor", from: "1.1.0"), // dev - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.47.0"), // dev - .package(url: "https://github.com/realm/SwiftLint", from: "0.43.0"), // dev - .package(url: "https://github.com/shibapm/Rocket.git", from: "1.2.0"), // dev - .package(url: "https://github.com/brightdigit/swift-test-codecov", from: "1.0.0") // dev + // Dependencies declare other packages that this package depends on. ], targets: [ - .target(name: "Prch"), + .target(name: "Prch", dependencies: ["PrchModel"]), + .target(name: "PrchModel", dependencies: []), .testTarget(name: "PrchTests", dependencies: ["Prch"]) ] ) - -#if canImport(PackageConfig) - import PackageConfig - - let requiredCoverage: Int = 85 - - let config = PackageConfiguration([ - "rocket": [ - "steps": [ - ["hide_dev_dependencies": ["package_path": "Package@swift-5.5.swift"]], - "hide_dev_dependencies", - "git_add", - "commit", - "tag", - "unhide_dev_dependencies", - ["unhide_dev_dependencies": ["package_path": "Package@swift-5.5.swift"]], - "git_add", - ["commit": ["message": "Unhide dev dependencies"]] - ] - ], - "komondor": [ - "pre-push": [ - "swift test --enable-code-coverage --enable-test-discovery" - // swiftlint:disable:next line_length - // "swift run swift-test-codecov .build/debug/codecov/SyndiKit.json --minimum \(requiredCoverage)" - ], - "pre-commit": [ - "swift test --enable-code-coverage --enable-test-discovery --generate-linuxmain", - "swift run swiftformat .", - "swift run swiftlint autocorrect", - "git add .", - "swift run swiftformat --lint .", - "swift run swiftlint" - ] - ] - ]).write() -#endif diff --git a/README.md b/README.md index 87b0187..693bfa0 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# Prch \ No newline at end of file +# floxbx + +A description of this package. diff --git a/Scripts/docc.sh b/Scripts/docc.sh new file mode 100755 index 0000000..3e4c918 --- /dev/null +++ b/Scripts/docc.sh @@ -0,0 +1,3 @@ +#!/bin/sh +xcodebuild docbuild -scheme SimulatorServices -derivedDataPath DerivedData -destination 'platform=macOS' +$(xcrun --find docc) process-archive transform-for-static-hosting DerivedData/Build/Products/Debug/SimulatorServices.doccarchive --output-path Output \ No newline at end of file diff --git a/Scripts/gh-md-toc b/Scripts/gh-md-toc new file mode 100755 index 0000000..8d35839 --- /dev/null +++ b/Scripts/gh-md-toc @@ -0,0 +1,411 @@ +#!/usr/bin/env bash + +# +# Steps: +# +# 1. Download corresponding html file for some README.md: +# curl -s $1 +# +# 2. Discard rows where no substring 'user-content-' (github's markup): +# awk '/user-content-/ { ... +# +# 3.1 Get last number in each row like ' ... sitemap.js.*<\/h/)+2, RLENGTH-5) +# +# 5. Find anchor and insert it inside "(...)": +# substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8) +# + +gh_toc_version="0.8.0" + +gh_user_agent="gh-md-toc v$gh_toc_version" + +# +# Download rendered into html README.md by its url. +# +# +gh_toc_load() { + local gh_url=$1 + + if type curl &>/dev/null; then + curl --user-agent "$gh_user_agent" -s "$gh_url" + elif type wget &>/dev/null; then + wget --user-agent="$gh_user_agent" -qO- "$gh_url" + else + echo "Please, install 'curl' or 'wget' and try again." + exit 1 + fi +} + +# +# Converts local md file into html by GitHub +# +# -> curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown +#

Hello world github/linguist#1 cool, and #1!

'" +gh_toc_md2html() { + local gh_file_md=$1 + local skip_header=$2 + + URL=https://api.github.com/markdown/raw + + if [ ! -z "$GH_TOC_TOKEN" ]; then + TOKEN=$GH_TOC_TOKEN + else + TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" + if [ -f "$TOKEN_FILE" ]; then + TOKEN="$(cat $TOKEN_FILE)" + fi + fi + if [ ! -z "${TOKEN}" ]; then + AUTHORIZATION="Authorization: token ${TOKEN}" + fi + + local gh_tmp_file_md=$gh_file_md + if [ "$skip_header" = "yes" ]; then + if grep -Fxq "" $gh_src; then + # cut everything before the toc + gh_tmp_file_md=$gh_file_md~~ + sed '1,//d' $gh_file_md > $gh_tmp_file_md + fi + fi + + # echo $URL 1>&2 + OUTPUT=$(curl -s \ + --user-agent "$gh_user_agent" \ + --data-binary @"$gh_tmp_file_md" \ + -H "Content-Type:text/plain" \ + -H "$AUTHORIZATION" \ + "$URL") + + rm -f $gh_file_md~~ + + if [ "$?" != "0" ]; then + echo "XXNetworkErrorXX" + fi + if [ "$(echo "${OUTPUT}" | awk '/API rate limit exceeded/')" != "" ]; then + echo "XXRateLimitXX" + else + echo "${OUTPUT}" + fi +} + + +# +# Is passed string url +# +gh_is_url() { + case $1 in + https* | http*) + echo "yes";; + *) + echo "no";; + esac +} + +# +# TOC generator +# +gh_toc(){ + local gh_src=$1 + local gh_src_copy=$1 + local gh_ttl_docs=$2 + local need_replace=$3 + local no_backup=$4 + local no_footer=$5 + local indent=$6 + local skip_header=$7 + + if [ "$gh_src" = "" ]; then + echo "Please, enter URL or local path for a README.md" + exit 1 + fi + + + # Show "TOC" string only if working with one document + if [ "$gh_ttl_docs" = "1" ]; then + + echo "Table of Contents" + echo "=================" + echo "" + gh_src_copy="" + + fi + + if [ "$(gh_is_url "$gh_src")" == "yes" ]; then + gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy" "$indent" + if [ "${PIPESTATUS[0]}" != "0" ]; then + echo "Could not load remote document." + echo "Please check your url or network connectivity" + exit 1 + fi + if [ "$need_replace" = "yes" ]; then + echo + echo "!! '$gh_src' is not a local file" + echo "!! Can't insert the TOC into it." + echo + fi + else + local rawhtml=$(gh_toc_md2html "$gh_src" "$skip_header") + if [ "$rawhtml" == "XXNetworkErrorXX" ]; then + echo "Parsing local markdown file requires access to github API" + echo "Please make sure curl is installed and check your network connectivity" + exit 1 + fi + if [ "$rawhtml" == "XXRateLimitXX" ]; then + echo "Parsing local markdown file requires access to github API" + echo "Error: You exceeded the hourly limit. See: https://developer.github.com/v3/#rate-limiting" + TOKEN_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt" + echo "or place GitHub auth token here: ${TOKEN_FILE}" + exit 1 + fi + local toc=`echo "$rawhtml" | gh_toc_grab "$gh_src_copy" "$indent"` + echo "$toc" + if [ "$need_replace" = "yes" ]; then + if grep -Fxq "" $gh_src && grep -Fxq "" $gh_src; then + echo "Found markers" + else + echo "You don't have or in your file...exiting" + exit 1 + fi + local ts="<\!--ts-->" + local te="<\!--te-->" + local dt=`date +'%F_%H%M%S'` + local ext=".orig.${dt}" + local toc_path="${gh_src}.toc.${dt}" + local toc_createdby="" + local toc_footer="" + # http://fahdshariff.blogspot.ru/2012/12/sed-mutli-line-replacement-between-two.html + # clear old TOC + sed -i${ext} "/${ts}/,/${te}/{//!d;}" "$gh_src" + # create toc file + echo "${toc}" > "${toc_path}" + if [ "${no_footer}" != "yes" ]; then + echo -e "\n${toc_createdby}\n${toc_footer}\n" >> "$toc_path" + fi + + # insert toc file + if ! sed --version > /dev/null 2>&1; then + sed -i "" "/${ts}/r ${toc_path}" "$gh_src" + else + sed -i "/${ts}/r ${toc_path}" "$gh_src" + fi + echo + if [ "${no_backup}" = "yes" ]; then + rm ${toc_path} ${gh_src}${ext} + fi + echo "!! TOC was added into: '$gh_src'" + if [ -z "${no_backup}" ]; then + echo "!! Origin version of the file: '${gh_src}${ext}'" + echo "!! TOC added into a separate file: '${toc_path}'" + fi + echo + fi + fi +} + +# +# Grabber of the TOC from rendered html +# +# $1 - a source url of document. +# It's need if TOC is generated for multiple documents. +# $2 - number of spaces used to indent. +# +gh_toc_grab() { + common_awk_script=' + modified_href = "" + split(href, chars, "") + for (i=1;i <= length(href); i++) { + c = chars[i] + res = "" + if (c == "+") { + res = " " + } else { + if (c == "%") { + res = "\\x" + } else { + res = c "" + } + } + modified_href = modified_href res + } + print sprintf("%*s", (level-1)*'"$2"', "") "* [" text "](" gh_url modified_href ")" + ' + if [ `uname -s` == "OS/390" ]; then + grepcmd="pcregrep -o" + echoargs="" + awkscript='{ + level = substr($0, length($0), 1) + text = substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5) + href = substr($0, match($0, "href=\"([^\"]+)?\"")+6, RLENGTH-7) + '"$common_awk_script"' + }' + else + grepcmd="grep -Eo" + echoargs="-e" + awkscript='{ + level = substr($0, length($0), 1) + text = substr($0, match($0, /a>.*<\/h/)+2, RLENGTH-5) + href = substr($0, match($0, "href=\"[^\"]+?\"")+6, RLENGTH-7) + '"$common_awk_script"' + }' + fi + href_regex='href=\"[^\"]+?\"' + + # if closed is on the new line, then move it on the prev line + # for example: + # was: The command foo1 + # + # became: The command foo1 + sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' | + + # find strings that corresponds to template + $grepcmd '//g' | sed 's/<\/code>//g' | + + # remove g-emoji + sed 's/]*[^<]*<\/g-emoji> //g' | + + # now all rows are like: + # ... /dev/null`; then + echo `$tool --version | head -n 1` + else + echo "not installed" + fi + done +} + +show_help() { + local app_name=$(basename "$0") + echo "GitHub TOC generator ($app_name): $gh_toc_version" + echo "" + echo "Usage:" + echo " $app_name [options] src [src] Create TOC for a README file (url or local path)" + echo " $app_name - Create TOC for markdown from STDIN" + echo " $app_name --help Show help" + echo " $app_name --version Show version" + echo "" + echo "Options:" + echo " --indent Set indent size. Default: 3." + echo " --insert Insert new TOC into original file. For local files only. Default: false." + echo " See https://github.com/ekalinin/github-markdown-toc/issues/41 for details." + echo " --no-backup Remove backup file. Set --insert as well. Default: false." + echo " --hide-footer Do not write date & author of the last TOC update. Set --insert as well. Default: false." + echo " --skip-header Hide entry of the topmost headlines. Default: false." + echo " See https://github.com/ekalinin/github-markdown-toc/issues/125 for details." + echo "" +} + +# +# Options handlers +# +gh_toc_app() { + local need_replace="no" + local indent=3 + + if [ "$1" = '--help' ] || [ $# -eq 0 ] ; then + show_help + return + fi + + if [ "$1" = '--version' ]; then + show_version + return + fi + + if [ "$1" = '--indent' ]; then + indent="$2" + shift 2 + fi + + if [ "$1" = "-" ]; then + if [ -z "$TMPDIR" ]; then + TMPDIR="/tmp" + elif [ -n "$TMPDIR" -a ! -d "$TMPDIR" ]; then + mkdir -p "$TMPDIR" + fi + local gh_tmp_md + if [ `uname -s` == "OS/390" ]; then + local timestamp=$(date +%m%d%Y%H%M%S) + gh_tmp_md="$TMPDIR/tmp.$timestamp" + else + gh_tmp_md=$(mktemp $TMPDIR/tmp.XXXXXX) + fi + while read input; do + echo "$input" >> "$gh_tmp_md" + done + gh_toc_md2html "$gh_tmp_md" | gh_toc_grab "" "$indent" + return + fi + + if [ "$1" = '--insert' ]; then + need_replace="yes" + shift + fi + + if [ "$1" = '--no-backup' ]; then + need_replace="yes" + no_backup="yes" + shift + fi + + if [ "$1" = '--hide-footer' ]; then + need_replace="yes" + no_footer="yes" + shift + fi + + if [ "$1" = '--skip-header' ]; then + skip_header="yes" + shift + fi + + + for md in "$@" + do + echo "" + gh_toc "$md" "$#" "$need_replace" "$no_backup" "$no_footer" "$indent" "$skip_header" + done + + echo "" + echo "" +} + +# +# Entry point +# +gh_toc_app "$@" diff --git a/Scripts/lint.sh b/Scripts/lint.sh new file mode 100755 index 0000000..c6b3d9a --- /dev/null +++ b/Scripts/lint.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +if [ -z "$SRCROOT" ]; then + SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + PACKAGE_DIR="${SCRIPT_DIR}/.." +else + PACKAGE_DIR="${SRCROOT}" +fi + +if [ -z "$GITHUB_ACTION" ]; then + MINT_CMD="/opt/homebrew/bin/mint" +else + MINT_CMD="mint" +fi + +export MINT_PATH="$PACKAGE_DIR/.mint" +MINT_ARGS="-n -m $PACKAGE_DIR/Mintfile --silent" +MINT_RUN="$MINT_CMD run $MINT_ARGS" + +pushd $PACKAGE_DIR + +$MINT_CMD bootstrap -m Mintfile + +if [ "$LINT_MODE" == "NONE" ]; then + exit +elif [ "$LINT_MODE" == "STRICT" ]; then + SWIFTFORMAT_OPTIONS="" + SWIFTLINT_OPTIONS="--strict" +else + SWIFTFORMAT_OPTIONS="" + SWIFTLINT_OPTIONS="" +fi + +pushd $PACKAGE_DIR + +if [ -z "$CI" ]; then + $MINT_RUN swiftformat . + $MINT_RUN swiftlint autocorrect +fi + +#$MINT_RUN periphery scan +$MINT_RUN swiftformat --lint $SWIFTFORMAT_OPTIONS . +$MINT_RUN swiftlint lint $SWIFTLINT_OPTIONS + +popd diff --git a/Sources/Prch/API.swift b/Sources/Prch/API.swift deleted file mode 100644 index de0b8e6..0000000 --- a/Sources/Prch/API.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -public protocol API { - var baseURL: URL { get } - var headers: [String: String] { get } - var decoder: ResponseDecoder { get } - var encoder: RequestEncoder { get } -} diff --git a/Sources/Prch/AnyCodable/AnyCodable.Codable.swift b/Sources/Prch/AnyCodable/AnyCodable.Codable.swift deleted file mode 100644 index 375b417..0000000 --- a/Sources/Prch/AnyCodable/AnyCodable.Codable.swift +++ /dev/null @@ -1,105 +0,0 @@ -import Foundation - -extension AnyCodable: Codable { - // swiftlint:disable:next cyclomatic_complexity function_body_length - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - - if container.decodeNil() { - self.init(()) - } else if let bool = try? container.decode(Bool.self) { - self.init(bool) - } else if let int = try? container.decode(Int.self) { - self.init(int) - } else if let uint = try? container.decode(UInt.self) { - self.init(uint) - } else if let double = try? container.decode(Double.self) { - self.init(double) - } else if let string = try? container.decode(String.self) { - self.init(string) - } else if let array = try? container.decode([AnyCodable].self) { - self.init(array.map { $0.value }) - } else if let dictionary = try? container.decode([String: AnyCodable].self) { - self.init(dictionary.mapValues { $0.value }) - } else { - throw DecodingError.dataCorruptedError( - in: container, - debugDescription: "AnyCodable value cannot be decoded" - ) - } - } - - // swiftlint:disable:next cyclomatic_complexity function_body_length - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - switch value { - case is Void: - try container.encodeNil() - - case let bool as Bool: - try container.encode(bool) - - case let int as Int: - try container.encode(int) - - case let int8 as Int8: - try container.encode(int8) - - case let int16 as Int16: - try container.encode(int16) - - case let int32 as Int32: - try container.encode(int32) - - case let int64 as Int64: - try container.encode(int64) - - case let uint as UInt: - try container.encode(uint) - - case let uint8 as UInt8: - try container.encode(uint8) - - case let uint16 as UInt16: - try container.encode(uint16) - - case let uint32 as UInt32: - try container.encode(uint32) - - case let uint64 as UInt64: - try container.encode(uint64) - - case let float as Float: - try container.encode(float) - - case let double as Double: - try container.encode(double) - - case let string as String: - try container.encode(string) - - case let date as Date: - try container.encode(date) - - case let url as URL: - try container.encode(url) - - case let array as [Any?]: - try container.encode(array.map { AnyCodable($0) }) - - case let dictionary as [String: Any?]: - try container.encode(dictionary.mapValues { AnyCodable($0) }) - - case let object as Encodable: - try object.encode(to: encoder) - - default: - let context = EncodingError.Context( - codingPath: container.codingPath, - debugDescription: "AnyCodable value cannot be encoded" - ) - throw EncodingError.invalidValue(value, context) - } - } -} diff --git a/Sources/Prch/AnyCodable/AnyCodable.Equatable.swift b/Sources/Prch/AnyCodable/AnyCodable.Equatable.swift deleted file mode 100644 index 047bea9..0000000 --- a/Sources/Prch/AnyCodable/AnyCodable.Equatable.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Foundation - -extension AnyCodable: Equatable { - // swiftlint:disable:next cyclomatic_complexity function_body_length - public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { - switch (lhs.value, rhs.value) { - case is (Void, Void): - return true - - case let (lhs as Bool, rhs as Bool): - return lhs == rhs - - case let (lhs as Int, rhs as Int): - return lhs == rhs - - case let (lhs as Int8, rhs as Int8): - return lhs == rhs - - case let (lhs as Int16, rhs as Int16): - return lhs == rhs - - case let (lhs as Int32, rhs as Int32): - return lhs == rhs - - case let (lhs as Int64, rhs as Int64): - return lhs == rhs - - case let (lhs as UInt, rhs as UInt): - return lhs == rhs - - case let (lhs as UInt8, rhs as UInt8): - return lhs == rhs - - case let (lhs as UInt16, rhs as UInt16): - return lhs == rhs - - case let (lhs as UInt32, rhs as UInt32): - return lhs == rhs - - case let (lhs as UInt64, rhs as UInt64): - return lhs == rhs - - case let (lhs as Float, rhs as Float): - return lhs == rhs - - case let (lhs as Double, rhs as Double): - return lhs == rhs - - case let (lhs as String, rhs as String): - return lhs == rhs - - case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]): - return lhs == rhs - - case let (lhs as [AnyCodable], rhs as [AnyCodable]): - return lhs == rhs - - default: - return false - } - } -} diff --git a/Sources/Prch/AnyCodable/AnyCodable.Expressible.swift b/Sources/Prch/AnyCodable/AnyCodable.Expressible.swift deleted file mode 100644 index 126ffb3..0000000 --- a/Sources/Prch/AnyCodable/AnyCodable.Expressible.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation - -extension AnyCodable: ExpressibleByNilLiteral, - ExpressibleByBooleanLiteral, - ExpressibleByIntegerLiteral, - ExpressibleByFloatLiteral, - ExpressibleByStringLiteral, - ExpressibleByArrayLiteral, - ExpressibleByDictionaryLiteral { - public init(nilLiteral _: ()) { - self.init(nil as Any?) - } - - public init(booleanLiteral value: Bool) { - self.init(value) - } - - public init(integerLiteral value: Int) { - self.init(value) - } - - public init(floatLiteral value: Double) { - self.init(value) - } - - public init(extendedGraphemeClusterLiteral value: String) { - self.init(value) - } - - public init(stringLiteral value: String) { - self.init(value) - } - - public init(arrayLiteral elements: Any...) { - self.init(elements) - } - - public init(dictionaryLiteral elements: (AnyHashable, Any)...) { - self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first })) - } -} diff --git a/Sources/Prch/AnyCodable/AnyCodable.StringConverable.swift b/Sources/Prch/AnyCodable/AnyCodable.StringConverable.swift deleted file mode 100644 index 33ddc6b..0000000 --- a/Sources/Prch/AnyCodable/AnyCodable.StringConverable.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -extension AnyCodable: CustomStringConvertible { - public var description: String { - switch value { - case is Void: - return String(describing: nil as Any?) - - case let value as CustomStringConvertible: - return value.description - - default: - return String(describing: value) - } - } -} - -extension AnyCodable: CustomDebugStringConvertible { - public var debugDescription: String { - switch value { - case let value as CustomDebugStringConvertible: - return "AnyCodable(\(value.debugDescription))" - default: - return "AnyCodable(\(description))" - } - } -} diff --git a/Sources/Prch/AnyCodable/AnyCodable.swift b/Sources/Prch/AnyCodable/AnyCodable.swift deleted file mode 100644 index 4ba5430..0000000 --- a/Sources/Prch/AnyCodable/AnyCodable.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -@available(*, deprecated, renamed: "AnyCodable") -public typealias CodableAny = AnyCodable - -public struct AnyCodable { - let value: Any - - init(_ value: T?) { - self.value = value ?? () - } -} diff --git a/Sources/Prch/AuthorizationManager.swift b/Sources/Prch/AuthorizationManager.swift new file mode 100644 index 0000000..14c87d5 --- /dev/null +++ b/Sources/Prch/AuthorizationManager.swift @@ -0,0 +1,4 @@ +public protocol AuthorizationManager { + associatedtype AuthorizationType + func fetch() async throws -> AuthorizationType? +} diff --git a/Sources/Prch/BasicResponse.swift b/Sources/Prch/BasicResponse.swift deleted file mode 100644 index 920e655..0000000 --- a/Sources/Prch/BasicResponse.swift +++ /dev/null @@ -1,65 +0,0 @@ -import Foundation - -public enum BasicResponse< - SuccessType: Decodable & StatusCodeProvider, - FailureType: Decodable, - APIType: API ->: Response { - public var response: ClientResult { - switch self { - case let .success(value): - return .success(value) - - case let .defaultResponse(statusCode, value): - return .defaultResponse(statusCode, value) - } - } - - public init(statusCode: Int, data: Data, decoder: ResponseDecoder) throws { - switch statusCode { - case SuccessType.statusCode: - self = try .success(decoder.decode(SuccessType.self, from: data)) - - default: - let value = try decoder.decode(FailureType.self, from: data) - self = .defaultResponse(statusCode, value) - } - } - - case success(SuccessType) - case defaultResponse(Int, FailureType) - - var anyResponse: Any { - switch self { - case let .defaultResponse(_, value): return value - case let .success(value): return value - } - } - - public var statusCode: Int { - switch self { - case let .defaultResponse(statusCode, _): return statusCode - case .success: return SuccessType.statusCode - } - } - - var successful: Bool { - switch self { - case .defaultResponse: return false - case .success: return true - } - } - - public var description: String { - "\(statusCode) \(successful ? "success" : "failure")" - } - - public var debugDescription: String { - var string = description - let responseString = "\(anyResponse)" - if responseString != "()" { - string += "\n\(responseString)" - } - return string - } -} diff --git a/Sources/Prch/BodyRequest.swift b/Sources/Prch/BodyRequest.swift deleted file mode 100644 index 31ffcd9..0000000 --- a/Sources/Prch/BodyRequest.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation - -public protocol BodyRequest: ServiceRequest { - associatedtype Body: Encodable - var body: Body { get } -} - -public extension BodyRequest { - var encodeBody: ((RequestEncoder) throws -> Data)? { - { defaultEncoder in - try defaultEncoder.encode(self.body) - } - } -} diff --git a/Sources/Prch/Client.swift b/Sources/Prch/Client.swift deleted file mode 100644 index eb515e0..0000000 --- a/Sources/Prch/Client.swift +++ /dev/null @@ -1,96 +0,0 @@ -import Foundation - -public class Client { - public init(api: APIType, session: SessionType) { - self.api = api - self.session = session - } - - public let api: APIType - public let session: SessionType - - @discardableResult - public func request( - _ request: RequestType, - _ completion: @escaping ( - ClientResponseResult) -> Void - ) -> Task? { - var sessionRequest: SessionType.RequestType - do { - sessionRequest = try session.createRequest( - request, - withBaseURL: api.baseURL, - andHeaders: api.headers, - usingEncoder: api.encoder - ) - } catch { - completion(.failure(ClientError.requestEncodingError(error))) - return nil - } - - return session.beginRequest(sessionRequest) { result in - let clientResult: Result = - .init(RequestType.ResponseType.self, result: result, decoder: self.api.decoder) - - completion(clientResult.response) - } - } -} - -#if compiler(>=5.5.2) && canImport(_Concurrency) - @available(iOS 13.0.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *) - public extension Client { - @available(swift 5.5) - func request( - _ request: RequestType - ) async throws -> RequestType.ResponseType.SuccessType { - let sessionRequest = try session.createRequest( - request, - withBaseURL: api.baseURL, - andHeaders: api.headers, - usingEncoder: api.encoder - ) - - let response = try await session.request(sessionRequest) - - guard let httpStatus = response.statusCode, let data = response.data else { - throw ClientError.invalidResponse - } - - return try RequestType.ResponseType( - statusCode: httpStatus, data: data, decoder: api.decoder - ).response.get() - } - } -#endif - -public extension Client { - func requestSync( - _ request: RequestType - ) throws -> RequestType.ResponseType.SuccessType { - try requestSync(request, timeout: .now() + 5.0) - } - - func requestSync( - _ request: RequestType, - timeout: @autoclosure () -> DispatchTime - ) throws -> RequestType.ResponseType.SuccessType { - var result: ClientResponseResult! - let semaphore = DispatchSemaphore(value: 0) - self.request(request) { - result = $0 - semaphore.signal() - } - - let timeoutValue = timeout() - let waitResult = semaphore.wait(timeout: timeoutValue) - - switch waitResult { - case .success: - return try result.get() - - case .timedOut: - throw ClientError.timeout(timeoutValue) - } - } -} diff --git a/Sources/Prch/ClientError.swift b/Sources/Prch/ClientError.swift deleted file mode 100644 index b0c13e1..0000000 --- a/Sources/Prch/ClientError.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation - -public enum ClientError: ResponseError { - case unexpectedStatusCode(statusCode: Int, data: Data) - case decodingError(DecodingError) - case requestEncodingError(Error) - case validationError(Error) - case networkError(Error) - case unknownError(Error) - case invalidResponse - case badURL(URL, String) - case urlComponents(URLComponents) - case timeout(DispatchTime) - - public var name: String { - switch self { - case .unexpectedStatusCode: return "Unexpected status code" - case .decodingError: return "Decoding error" - case .validationError: return "Request validation failed" - case .requestEncodingError: return "Request encoding failed" - case .networkError: return "Network error" - case .unknownError: return "Unknown error" - case .invalidResponse: return "Invalid Response" - case .badURL: return "Bad URL" - case .urlComponents: return "Bad URL Components" - case .timeout: return "Request timed out." - } - } -} - -extension ClientError: CustomStringConvertible { - public var description: String { - switch self { - case let .unexpectedStatusCode(statusCode, _): return "\(name): \(statusCode)" - case let .decodingError(error): - return "\(name): \(error.localizedDescription)\n\(error)" - case let .validationError(error): return "\(name): \(error.localizedDescription)" - case let .requestEncodingError(error): return "\(name): \(error)" - case let .networkError(error): return "\(name): \(error.localizedDescription)" - case let .unknownError(error): return "\(name): \(error.localizedDescription)" - case .invalidResponse: return "\(name)" - case let .badURL(url, path): return "\(name): \(url) \(path)" - case let .urlComponents(components): return "\(name): \(components)" - case let .timeout(timeout): return "\(name): \(timeout)" - } - } -} diff --git a/Sources/Prch/ClientResult.swift b/Sources/Prch/ClientResult.swift deleted file mode 100644 index 231aac0..0000000 --- a/Sources/Prch/ClientResult.swift +++ /dev/null @@ -1,72 +0,0 @@ -import Foundation - -public enum ClientResult: CustomStringConvertible, - CustomDebugStringConvertible { - case success(SuccessType) - case defaultResponse(Int, DefaultResponseType) - case failure(ClientError) - - var value: Any { - switch self { - case let .success(value): return value - case let .defaultResponse(_, value): return value - case let .failure(value): return value - } - } - - var successful: Bool { - switch self { - case .success: return true - case .defaultResponse: return false - case .failure: return false - } - } - - public var description: String { - "\(successful ? "success" : "failure")" - } - - public var debugDescription: String { - "\(description):\n\(value)" - } -} - -public typealias ClientResponseResult - = ClientResult - where ResponseType: Response - -public protocol ResponseError: Error {} - -public extension ClientResult { - struct FailedResponseError: ResponseError { - public let statusCode: Int - public let response: DefaultResponseType - } - - func get() throws -> SuccessType { - switch self { - case let .success(value): - return value - - case let .failure(error): - throw error - - case let .defaultResponse(statusCode, response): - throw FailedResponseError(statusCode: statusCode, response: response) - } - } -} - -extension Result { - init( - response: ClientResult - ) where Failure == Error { - let value: Success - do { - value = try response.get() - self = .success(value) - } catch { - self = .failure(error) - } - } -} diff --git a/Sources/Prch/Coding/Coding.swift b/Sources/Prch/Coding/Coding.swift deleted file mode 100644 index 74345b5..0000000 --- a/Sources/Prch/Coding/Coding.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation - -public extension Date { - func encode(with dateEncodingFormatter: DateFormatter) -> Any { - dateEncodingFormatter.string(from: self) - } -} - -public extension Optional where Wrapped == Date { - func encode(with dateEncodingFormatter: DateFormatter) -> Any? { - self?.encode(with: dateEncodingFormatter) - } -} - -public extension URL { - func encode() -> Any { - absoluteString - } -} - -public extension RawRepresentable { - func encode() -> Any { - rawValue - } -} - -public extension Array where Element: RawRepresentable { - func encode() -> [Any] { - map { $0.rawValue } - } -} - -public extension Dictionary where Key == String, Value: RawRepresentable { - func encode() -> [String: Any] { - mapValues { $0.rawValue } - } -} - -public extension UUID { - func encode() -> Any { - uuidString - } -} - -extension String { - func encode() -> Any { - self - } -} - -extension Data { - func encode() -> Any { - self - } -} diff --git a/Sources/Prch/Coding/DateFormatter.swift b/Sources/Prch/Coding/DateFormatter.swift deleted file mode 100644 index 62bb064..0000000 --- a/Sources/Prch/Coding/DateFormatter.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -public extension DateFormatter { - convenience init( - formatString: String, - locale: Locale? = nil, - timeZone: TimeZone? = nil, - calendar: Calendar? = nil - ) { - self.init() - dateFormat = formatString - if let locale = locale { - self.locale = locale - } - if let timeZone = timeZone { - self.timeZone = timeZone - } - if let calendar = calendar { - self.calendar = calendar - } - } - - convenience init(dateStyle: DateFormatter.Style, timeStyle: DateFormatter.Style) { - self.init() - self.dateStyle = dateStyle - self.timeStyle = timeStyle - } -} - -public extension DateFormatter { - func string(from dateDay: DateDay) -> String { - string(from: dateDay.date) - } -} diff --git a/Sources/Prch/Coding/DecodingError.swift b/Sources/Prch/Coding/DecodingError.swift deleted file mode 100644 index a2d53df..0000000 --- a/Sources/Prch/Coding/DecodingError.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -public extension DecodingError { - static func mismatch( - ofType type: MismatchType.Type, - withCodingPath codingPath: [CodingKey]? = nil - ) -> DecodingError { - let codingPath = codingPath ?? [StringCodingKey(stringValue: "")] - return DecodingError.typeMismatch( - MismatchType.self, - DecodingError.Context( - codingPath: codingPath, - debugDescription: "Decoding of \(type) failed" - ) - ) - } -} diff --git a/Sources/Prch/Coding/KeyedDecodingContainer.swift b/Sources/Prch/Coding/KeyedDecodingContainer.swift deleted file mode 100644 index cae50b8..0000000 --- a/Sources/Prch/Coding/KeyedDecodingContainer.swift +++ /dev/null @@ -1,125 +0,0 @@ -import Foundation - -// any decoding -public extension KeyedDecodingContainer { - func decodeAny(_: T.Type, forKey key: K) throws -> T { - guard let value = try decode(AnyCodable.self, forKey: key).value as? T else { - throw DecodingError.mismatch(ofType: T.self, withCodingPath: codingPath) - } - return value - } - - func decodeAnyCodableDictionary( - _ key: K) throws -> [String: AnyCodable]? { - guard let value = try decodeIfPresent( - AnyCodable.self, forKey: key - )?.value else { return nil } - if let typedValue = value as? [String: Any] { - return typedValue.mapValues(AnyCodable.init) - } else { - throw DecodingError.mismatch( - ofType: [String: AnyCodable].self, - withCodingPath: codingPath - ) - } - } - - func decodeAnyIfPresent(_: T.Type, forKey key: K) throws -> T? { - try decodeOptional { - guard let value = try decodeIfPresent( - AnyCodable.self, forKey: key - )?.value else { return nil } - if let typedValue = value as? T { - return typedValue - } else { - throw DecodingError.mismatch(ofType: T.self, withCodingPath: codingPath) - } - } - } - - func toDictionary() throws -> [String: Any] { - var dictionary: [String: Any] = [:] - for key in allKeys { - dictionary[key.stringValue] = try decodeAny(key) - } - return dictionary - } - - func decode(_ key: KeyedDecodingContainer.Key) throws -> T where T: Decodable { - try decode(T.self, forKey: key) - } - - func decodeIfPresent( - _ key: KeyedDecodingContainer.Key, - safeOptionalDecoding: Bool = false - ) throws -> T? where T: Decodable { - try decodeOptional({ - try decodeIfPresent(T.self, forKey: key) - }, safeOptionalDecoding: safeOptionalDecoding) - } - - func decodeAny(_ key: K) throws -> T { - try decodeAny(T.self, forKey: key) - } - - func decodeAnyIfPresent(_ key: K) throws -> T? { - try decodeAnyIfPresent(T.self, forKey: key) - } - - func decodeArray( - _ key: K, - safeArrayDecoding: Bool = false - ) throws -> [T] { - var container: UnkeyedDecodingContainer - var array: [T] = [] - - do { - container = try nestedUnkeyedContainer(forKey: key) - } catch { - if safeArrayDecoding { - return array - } else { - throw error - } - } - - while !container.isAtEnd { - do { - let element = try container.decode(T.self) - array.append(element) - } catch { - if safeArrayDecoding { - // hack to advance the current index - _ = try? container.decode(AnyCodable.self) - } else { - throw error - } - } - } - return array - } - - func decodeArrayIfPresent(_ key: K) throws -> [T]? { - try decodeOptional { - if contains(key) { - return try decodeArray(key) - } else { - return nil - } - } - } - - private func decodeOptional( - _ closure: () throws -> T?, safeOptionalDecoding: Bool = false - ) throws -> T? { - if safeOptionalDecoding { - do { - return try closure() - } catch { - return nil - } - } else { - return try closure() - } - } -} diff --git a/Sources/Prch/Coding/KeyedEncodingContainer.swift b/Sources/Prch/Coding/KeyedEncodingContainer.swift deleted file mode 100644 index d9d6ffd..0000000 --- a/Sources/Prch/Coding/KeyedEncodingContainer.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -// any encoding -public extension KeyedEncodingContainer { - mutating func encodeAnyIfPresent(_ value: T?, forKey key: K) throws { - guard let value = value else { return } - try encodeIfPresent(AnyCodable(value), forKey: key) - } - - mutating func encodeAny(_ value: T, forKey key: K) throws { - try encode(AnyCodable(value), forKey: key) - } -} diff --git a/Sources/Prch/Coding/RequestEncoder.swift b/Sources/Prch/Coding/RequestEncoder.swift deleted file mode 100644 index 4f07dcb..0000000 --- a/Sources/Prch/Coding/RequestEncoder.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -public protocol RequestEncoder { - func encode(_ value: T) throws -> Data -} - -extension JSONEncoder: RequestEncoder {} diff --git a/Sources/Prch/Coding/ResponseDecoder.swift b/Sources/Prch/Coding/ResponseDecoder.swift deleted file mode 100644 index b260132..0000000 --- a/Sources/Prch/Coding/ResponseDecoder.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - -public protocol ResponseDecoder { - func decode(_ type: T.Type, from: Data) throws -> T -} - -extension JSONDecoder: ResponseDecoder {} - -public extension ResponseDecoder { - func decodeAny(_: T.Type, from data: Data) throws -> T { - guard let decoded = try decode(AnyCodable.self, from: data) as? T else { - throw DecodingError.mismatch(ofType: T.self) - } - return decoded - } -} diff --git a/Sources/Prch/Coding/StringCodingKey.swift b/Sources/Prch/Coding/StringCodingKey.swift deleted file mode 100644 index 8dc79f3..0000000 --- a/Sources/Prch/Coding/StringCodingKey.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation -public struct StringCodingKey: CodingKey, ExpressibleByStringLiteral { - public let stringValue: String - public let intValue: Int? - - public init(stringValue: String) { - self.stringValue = stringValue - intValue = nil - } - - public init?(intValue: Int) { - stringValue = String(describing: intValue) - self.intValue = intValue - } - - public init(stringLiteral value: String) { - self.init(stringValue: value) - } -} diff --git a/Sources/Prch/DateEncodingContainer.swift b/Sources/Prch/DateEncodingContainer.swift deleted file mode 100644 index d6cee38..0000000 --- a/Sources/Prch/DateEncodingContainer.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -public protocol DateEncodingContainer { - static var dateEncodingFormatter: DateFormatter { get } -} - -public extension DateEncodingContainer where Self: API { - var decoder: ResponseDecoder { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(Self.dateEncodingFormatter) - return decoder - } - - var encoder: RequestEncoder { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .formatted(Self.dateEncodingFormatter) - return encoder - } -} diff --git a/Sources/Prch/Deprecated/DeprecatedRequest.swift b/Sources/Prch/Deprecated/DeprecatedRequest.swift deleted file mode 100644 index 5a1a979..0000000 --- a/Sources/Prch/Deprecated/DeprecatedRequest.swift +++ /dev/null @@ -1,59 +0,0 @@ -import Foundation - -@available(*, deprecated, message: "use `Request`") -open class DeprecatedRequest< - ResponseType: Response, APIType ->: Request where ResponseType.APIType == APIType { - public let service: Service - open private(set) var queryParameters: [String: Any] - open private(set) var formParameters: [String: Any] - public let encodeBody: ((RequestEncoder) throws -> Data)? - private(set) var headerParameters: [String: String] - public var customHeaders: [String: String] = [:] - - public var headers: [String: String] { - headerParameters.merging(customHeaders) { _, custom in custom } - } - - public var name: String { - service.name - } - - open var path: String { - service.path - } - - open var method: String { - service.method - } - - public init(service: Service, - queryParameters: [String: Any] = [:], - formParameters: [String: Any] = [:], - headers: [String: String] = [:], - encodeBody: ((RequestEncoder) throws -> Data)? = nil) { - self.service = service - self.queryParameters = queryParameters - self.formParameters = formParameters - headerParameters = headers - self.encodeBody = encodeBody - } - - public var description: String { - var string = "\(service.name): \(service.method) \(path)" - if !queryParameters.isEmpty { - string += "?" + queryParameters.map { "\($0)=\($1)" }.joined(separator: "&") - } - return string - } - - public var debugDescription: String { - var string = description - if let encodeBody = encodeBody, - let data = try? encodeBody(JSONEncoder()), - let bodyString = String(data: data, encoding: .utf8) { - string += "\nbody: \(bodyString)" - } - return string - } -} diff --git a/Sources/Prch/Deprecated/DeprecatedResponse.swift b/Sources/Prch/Deprecated/DeprecatedResponse.swift deleted file mode 100644 index 79229a6..0000000 --- a/Sources/Prch/Deprecated/DeprecatedResponse.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -@available(*, deprecated, message: "Use DecodedResponse.") -public protocol DeprecatedResponse: Response { - var success: SuccessType? { get } - - var failure: FailureType? { get } -} diff --git a/Sources/Prch/Deprecated/DeprecatedResponseResult.swift b/Sources/Prch/Deprecated/DeprecatedResponseResult.swift deleted file mode 100644 index d273fe6..0000000 --- a/Sources/Prch/Deprecated/DeprecatedResponseResult.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Foundation - -public enum DeprecatedResponseResult< - SuccessType, DefaultResponseType ->: CustomStringConvertible, CustomDebugStringConvertible { - case success(SuccessType) - case failure(DefaultResponseType) - - var value: Any { - switch self { - case let .success(value): return value - case let .failure(value): return value - } - } - - var successful: Bool { - switch self { - case .success: return true - case .failure: return false - } - } - - public var description: String { - "\(successful ? "success" : "failure")" - } - - public var debugDescription: String { - "\(description):\n\(value)" - } -} diff --git a/Sources/Prch/Deprecated/TypeAliases.swift b/Sources/Prch/Deprecated/TypeAliases.swift deleted file mode 100644 index 7d45ef2..0000000 --- a/Sources/Prch/Deprecated/TypeAliases.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -@available(*, deprecated, renamed: "Client") -public typealias APIClient = Client - -@available(*, deprecated, renamed: "ClientError") -public typealias APIClientError = ClientError - -@available(*, deprecated, renamed: "Date") -public typealias DateTime = Date? - -@available(*, deprecated, renamed: "Service") -public typealias APIService = Service - -@available(*, deprecated, renamed: "DeprecatedRequest") -public typealias APIRequest = DeprecatedRequest - -@available(*, deprecated, renamed: "DeprecatedResponse") -public typealias APIResponseValue = DeprecatedResponse diff --git a/Sources/Prch/Model.swift b/Sources/Prch/Model.swift deleted file mode 100644 index 939b051..0000000 --- a/Sources/Prch/Model.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -public protocol Model: Codable, Equatable {} - -extension Model { - func encode() -> [String: Any] { - guard - let jsonData = try? JSONEncoder().encode(self), - let jsonValue = try? JSONSerialization.jsonObject(with: jsonData), - let jsonDictionary = jsonValue as? [String: Any] else { - return [:] - } - return jsonDictionary - } -} diff --git a/Sources/Prch/Models/DateDay.swift b/Sources/Prch/Models/DateDay.swift deleted file mode 100644 index 2b1c476..0000000 --- a/Sources/Prch/Models/DateDay.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Foundation - -public struct DateDay: Codable, Comparable { - /// The date formatter used for encoding and decoding - public static let dateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - formatter.calendar = .current - return formatter - }() - - public let date: Date - public let year: Int - public let month: Int - public let day: Int - - public init(date: Date = Date()) { - self.date = date - let dateComponents = Calendar.current.dateComponents( - [.day, .month, .year], - from: date - ) - guard let year = dateComponents.year, - let month = dateComponents.month, - let day = dateComponents.day else { - fatalError("Date does not contain correct components") - } - self.year = year - self.month = month - self.day = day - } - - public init(year: Int, month: Int, day: Int) { - let dateComponents = DateComponents( - calendar: .current, year: year, month: month, day: day - ) - guard let date = dateComponents.date else { - fatalError("Could not create date in current calendar") - } - self.date = date - self.year = year - self.month = month - self.day = day - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let string = try container.decode(String.self) - guard let date = DateDay.dateFormatter.date(from: string) else { - let dateFormat = DateDay.dateFormatter.dateFormat ?? "" - throw DecodingError.dataCorruptedError( - in: container, - debugDescription: "Date not in correct format of \(dateFormat)" - ) - } - self.init(date: date) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - let string = DateDay.dateFormatter.string(from: date) - try container.encode(string) - } - - public static func == (lhs: DateDay, rhs: DateDay) -> Bool { - lhs.year == rhs.year && - lhs.month == rhs.month && - lhs.day == rhs.day - } - - public static func < (lhs: DateDay, rhs: DateDay) -> Bool { - lhs.date < rhs.date - } -} - -// for parameter encoding - -public extension DateDay { - func encode() -> Any { - DateDay.dateFormatter.string(from: date) - } -} diff --git a/Sources/Prch/Models/File.swift b/Sources/Prch/Models/File.swift deleted file mode 100644 index 044fd31..0000000 --- a/Sources/Prch/Models/File.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -public typealias File = Data diff --git a/Sources/Prch/Models/ID.swift b/Sources/Prch/Models/ID.swift deleted file mode 100644 index 52ef2c6..0000000 --- a/Sources/Prch/Models/ID.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -public typealias ID = UUID diff --git a/Sources/Prch/Models/UploadFile.swift b/Sources/Prch/Models/UploadFile.swift deleted file mode 100644 index 0dafd3c..0000000 --- a/Sources/Prch/Models/UploadFile.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -/// A file upload -public struct UploadFile: Equatable { - public let type: FileType - public let fileName: String? - public let mimeType: String? - - public init(type: FileType) { - self.type = type - fileName = nil - mimeType = nil - } - - public init(type: FileType, fileName: String, mimeType: String) { - self.type = type - self.fileName = fileName - self.mimeType = mimeType - } - - public enum FileType: Equatable { - case data(Data) - case url(URL) - } - - func encode() -> Any { - self - } -} diff --git a/Sources/Prch/Networking/ResponseComponents.swift b/Sources/Prch/Networking/ResponseComponents.swift deleted file mode 100644 index 1f06464..0000000 --- a/Sources/Prch/Networking/ResponseComponents.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation - -public protocol ResponseComponents { - var statusCode: Int? { get } - var data: Data? { get } -} diff --git a/Sources/Prch/Networking/Session.swift b/Sources/Prch/Networking/Session.swift deleted file mode 100644 index 664cb69..0000000 --- a/Sources/Prch/Networking/Session.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -public protocol Session { - associatedtype RequestType - func createRequest( - _ request: RequestType, - withBaseURL baseURL: URL, - andHeaders headers: [String: String], - usingEncoder encoder: RequestEncoder - ) throws -> Self.RequestType - - @discardableResult func beginRequest( - _ request: RequestType, - _ completion: @escaping ((Result) -> Void) - ) -> Task - - #if compiler(>=5.5.2) && canImport(_Concurrency) - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - func request( - _ request: RequestType - ) async throws -> ResponseComponents - #endif -} diff --git a/Sources/Prch/Networking/Task.swift b/Sources/Prch/Networking/Task.swift deleted file mode 100644 index 3b0f824..0000000 --- a/Sources/Prch/Networking/Task.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -public protocol Task {} - -extension URLSessionDataTask: Task {} diff --git a/Sources/Prch/Networking/URL.swift b/Sources/Prch/Networking/URL.swift deleted file mode 100644 index dfe25af..0000000 --- a/Sources/Prch/Networking/URL.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public extension URL { - init(staticString string: S) { - guard let url = URL(string: "\(string)") else { - preconditionFailure("Invalid static URL string: \(string)") - } - - self = url - } -} diff --git a/Sources/Prch/Networking/URLSession.swift b/Sources/Prch/Networking/URLSession.swift deleted file mode 100644 index e3ee6ff..0000000 --- a/Sources/Prch/Networking/URLSession.swift +++ /dev/null @@ -1,96 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -extension URLSession: Session { - #if compiler(>=5.5.2) && canImport(_Concurrency) - @available(macOS, deprecated: 12.0) - @available(iOS, deprecated: 15.0) - @available(watchOS, deprecated: 8.0) - @available(tvOS, deprecated: 15.0) - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - fileprivate func responseComponentsFrom( - _ request: URLRequest - ) async throws -> ResponseComponents { - try await withCheckedThrowingContinuation { continuation in - _ = self.beginRequest(request) { result in - continuation.resume(with: result) - } - } - } - - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) - public func request(_ request: RequestType) async throws -> ResponseComponents { - #if canImport(FoundationNetworking) - return try await responseComponentsFrom(request) - #else - if #available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) { - let components = try await self.data(for: request) - return URLSessionResponse(components) - } else { - return try await responseComponentsFrom(request) - } - #endif - } - #endif - - public func beginRequest( - _ request: URLRequest, - _ completion: @escaping ((Result) -> Void) - ) -> Task { - let task = dataTask(with: request) { data, response, error in - let result = URLSessionResponse.resultBasedOnResponse( - response, - data: data, - error: error - ) - completion(result) - } - task.resume() - return task - } - - public func createRequest( - _ request: RequestType, - withBaseURL baseURL: URL, - andHeaders headers: [String: String], - usingEncoder encoder: RequestEncoder - ) throws -> URLRequest { - guard var componenets = URLComponents( - url: baseURL.appendingPathComponent(request.path), - resolvingAgainstBaseURL: false - ) else { - throw ClientError.badURL(baseURL, request.path) - } - - // filter out parameters with empty string value - var queryItems = [URLQueryItem]() - for (key, value) in request.queryParameters { - if !String(describing: value).isEmpty { - queryItems.append(URLQueryItem(name: key, value: String(describing: value))) - } - } - componenets.queryItems = queryItems - - guard let url = componenets.url else { - throw ClientError.urlComponents(componenets) - } - - var urlRequest = URLRequest(url: url) - urlRequest.httpMethod = request.method - - urlRequest.allHTTPHeaderFields = request.headers.merging( - headers, - uniquingKeysWith: { requestHeaderKey, _ in - requestHeaderKey - } - ) - - if let encodeBody = request.encodeBody { - urlRequest.httpBody = try encodeBody(encoder) - } - return urlRequest - } -} diff --git a/Sources/Prch/Networking/URLSessionResponse.swift b/Sources/Prch/Networking/URLSessionResponse.swift deleted file mode 100644 index 9a4d213..0000000 --- a/Sources/Prch/Networking/URLSessionResponse.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -struct URLSessionResponse: ResponseComponents { - init(data: Data?, response: URLResponse?) { - self.data = data - self.response = response - } - - let data: Data? - let response: URLResponse? - - var statusCode: Int? { - (response as? HTTPURLResponse)?.statusCode - } - - static func resultBasedOnResponse( - _ response: URLResponse?, - data: Data?, - error: Error? - ) -> Result { - if let error = error { - return .failure(.networkError(error)) - } else { - return .success(URLSessionResponse(data: data, response: response)) - } - } -} - -extension URLSessionResponse { - init(_ components: (Data, URLResponse)) { - self.init(data: components.0, response: components.1) - } -} diff --git a/Sources/Prch/NullAuthorizationManager.swift b/Sources/Prch/NullAuthorizationManager.swift new file mode 100644 index 0000000..b7b1285 --- /dev/null +++ b/Sources/Prch/NullAuthorizationManager.swift @@ -0,0 +1,9 @@ +public struct NullAuthorizationManager: AuthorizationManager { + public func fetch() async throws -> AuthorizationType? { + nil + } + + public typealias AuthorizationType = AuthorizationType + + public init() {} +} diff --git a/Sources/Prch/Request.swift b/Sources/Prch/Request.swift deleted file mode 100644 index 27dd86a..0000000 --- a/Sources/Prch/Request.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -public protocol Request: CustomStringConvertible, CustomDebugStringConvertible { - associatedtype ResponseType: Response - typealias APIType = ResponseType.APIType - var method: String { get } - var path: String { get } - var queryParameters: [String: Any] { get } - var headers: [String: String] { get } - var encodeBody: ((RequestEncoder) throws -> Data)? { get } - var name: String { get } -} - -public extension Request { - var description: String { - var string = "\(name): \(method) \(path)" - if !queryParameters.isEmpty { - string += "?" + queryParameters.map { "\($0)=\($1)" }.joined(separator: "&") - } - return string - } - - var debugDescription: String { - var string = description - if let encodeBody = encodeBody, - let data = try? encodeBody(JSONEncoder()), - let bodyString = String(data: data, encoding: .utf8) { - string += "\nbody: \(bodyString)" - } - return string - } -} diff --git a/Sources/Prch/Response.swift b/Sources/Prch/Response.swift deleted file mode 100644 index 166cbd4..0000000 --- a/Sources/Prch/Response.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public protocol Response: CustomDebugStringConvertible, CustomStringConvertible { - associatedtype SuccessType - associatedtype FailureType - associatedtype APIType: API - var statusCode: Int { get } - var response: ClientResult { get } - typealias ResponseDecodedType = ResponseResult - init(statusCode: Int, data: Data, decoder: ResponseDecoder) throws -} diff --git a/Sources/Prch/ResponseResult.swift b/Sources/Prch/ResponseResult.swift deleted file mode 100644 index e9ca829..0000000 --- a/Sources/Prch/ResponseResult.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation - -public enum ResponseResult { - case success(SuccessType) - case defaultResponse(Int, FailureType) -} - -extension ResponseResult { - var response: ClientResult { - switch self { - case let .success(value): - return .success(value) - - case let .defaultResponse(statusCode, value): - return .defaultResponse(statusCode, value) - } - } -} - -extension Result { - init( - response: ResponseResult - ) where Failure == Error { - let value: Success - do { - value = try response.get() - self = .success(value) - } catch { - self = .failure(error) - } - } -} diff --git a/Sources/Prch/Security/Authentication.swift b/Sources/Prch/Security/Authentication.swift deleted file mode 100644 index f488293..0000000 --- a/Sources/Prch/Security/Authentication.swift +++ /dev/null @@ -1,4 +0,0 @@ -protocol Authentication { - static var name: String { get } - var value: String { get } -} diff --git a/Sources/Prch/Security/BasicAuthentication.swift b/Sources/Prch/Security/BasicAuthentication.swift deleted file mode 100644 index 87585be..0000000 --- a/Sources/Prch/Security/BasicAuthentication.swift +++ /dev/null @@ -1,13 +0,0 @@ -public struct BasicAuthentication: Authentication { - public init(userName: String, password: String) { - self.userName = userName - self.password = password - } - - public static let name = "Basic" - let userName: String - let password: String - public var value: String { - "\(userName):\(password)".data(using: .utf8)?.base64EncodedString() ?? "" - } -} diff --git a/Sources/Prch/Security/SecurityRequirement.swift b/Sources/Prch/Security/SecurityRequirement.swift deleted file mode 100644 index 4d16186..0000000 --- a/Sources/Prch/Security/SecurityRequirement.swift +++ /dev/null @@ -1,9 +0,0 @@ -public struct SecurityRequirement { - public let type: String - public let scopes: [String] - - public init(type: String, scopes: [String]) { - self.type = type - self.scopes = scopes - } -} diff --git a/Sources/Prch/Service.swift b/Sources/Prch/Service.swift index 2b1a7f9..6eb042f 100644 --- a/Sources/Prch/Service.swift +++ b/Sources/Prch/Service.swift @@ -1,35 +1,41 @@ -public struct Service { - public let id: String - public let tag: String - public let method: String - public let path: String - public let hasBody: Bool - public let isUpload: Bool - public let securityRequirements: [SecurityRequirement] +import Foundation +import PrchModel - public init(id: String, - tag: String = "", - method: String, - path: String, - hasBody: Bool, - isUpload: Bool = false, - securityRequirements: [SecurityRequirement] = []) { - self.id = id - self.tag = tag - self.method = method - self.path = path - self.hasBody = hasBody - self.isUpload = isUpload - self.securityRequirements = securityRequirements - } +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public protocol Service: ServiceProtocol { + typealias SessionAuthenticationManager = + AuthorizationManager + associatedtype SessionType: Session + where SessionType.ResponseType.DataType == ServiceAPI.ResponseDataType + var authorizationManager: any SessionAuthenticationManager { get } + var session: SessionType { get } + var api: ServiceAPI { get } } -extension Service: CustomStringConvertible { - public var name: String { - "\(tag.isEmpty ? "" : "\(tag).")\(id)" - } +extension Service { + public func request( + _ request: RequestType + ) async throws -> RequestType.SuccessType.DecodableType + where RequestType: ServiceCall, RequestType.ServiceAPI == Self.ServiceAPI, + SessionType.RequestDataType == Self.ServiceAPI.RequestDataType { + let response = try await session.data( + request: request, + withBaseURL: api.baseURLComponents, + withHeaders: api.headers, + authorizationManager: authorizationManager, + usingEncoder: request.resolveEncoder(with: api) + ) + + guard request.isValidStatusCode(response.statusCode) else { + throw RequestError.invalidStatusCode(response.statusCode) + } - public var description: String { - "\(name): \(method) \(path)" + return try request.resolveDecoder(with: api).decodeContent( + RequestType.SuccessType.self, + from: response.data + ) } } diff --git a/Sources/Prch/ServiceProtocol.swift b/Sources/Prch/ServiceProtocol.swift index f20a7ea..4498af4 100644 --- a/Sources/Prch/ServiceProtocol.swift +++ b/Sources/Prch/ServiceProtocol.swift @@ -2,7 +2,9 @@ import Foundation import PrchModel public protocol ServiceProtocol { + associatedtype ServiceAPI: API func request( _ request: RequestType ) async throws -> RequestType.SuccessType.DecodableType + where RequestType.ServiceAPI == Self.ServiceAPI } diff --git a/Sources/Prch/ServiceRequest.swift b/Sources/Prch/ServiceRequest.swift deleted file mode 100644 index 0a24219..0000000 --- a/Sources/Prch/ServiceRequest.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation - -public protocol ServiceRequest: Request { - var service: Service { get } -} - -public extension ServiceRequest { - var name: String { - service.name - } - - var method: String { - service.method - } - - var path: String { - service.path - } - - var queryParameters: [String: Any] { - [:] - } - - var headers: [String: String] { - [:] - } - - var encodeBody: ((RequestEncoder) throws -> Data)? { - nil - } -} diff --git a/Sources/Prch/Session/LegacyCoder.swift b/Sources/Prch/Session/LegacyCoder.swift deleted file mode 100644 index 3f2ff2d..0000000 --- a/Sources/Prch/Session/LegacyCoder.swift +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Sources/Prch/Session/Session.swift b/Sources/Prch/Session/Session.swift index 97c4676..60d107e 100644 --- a/Sources/Prch/Session/Session.swift +++ b/Sources/Prch/Session/Session.swift @@ -1,21 +1,16 @@ -public protocol Session { - associatedtype SessionRequestType: SessionRequest - associatedtype SessionResponseType: SessionResponse - @discardableResult - func request( - _ request: SessionRequestType, - _ completed: @escaping (Result) -> Void - ) -> SessionTask - - - func request (_ request: SessionRequestType) async throws -> SessionResponseType -} +import Foundation +import PrchModel +public protocol Session { + associatedtype RequestDataType + associatedtype ResponseType: SessionResponse + associatedtype AuthorizationType -extension Session { - public func request (_ request: SessionRequestType) async throws -> SessionResponseType { - try await withCheckedThrowingContinuation{ continuation in - self.request(request, continuation.resume(with:)) - } - } + func data( + request: CallType, + withBaseURL baseURLComponents: URLComponents, + withHeaders headers: [String: String], + authorizationManager: any AuthorizationManager, + usingEncoder encoder: any Encoder + ) async throws -> ResponseType } diff --git a/Sources/Prch/Session/URLSession/URLSessionAuthorization.swift b/Sources/Prch/Session/SessionAuthorization.swift similarity index 60% rename from Sources/Prch/Session/URLSession/URLSessionAuthorization.swift rename to Sources/Prch/Session/SessionAuthorization.swift index 48aa96c..b430e22 100644 --- a/Sources/Prch/Session/URLSession/URLSessionAuthorization.swift +++ b/Sources/Prch/Session/SessionAuthorization.swift @@ -1,5 +1,5 @@ import Foundation -public protocol URLSessionAuthorization { +public protocol SessionAuthorization { var httpHeaders: [String: String] { get } } diff --git a/Sources/Prch/Session/URLSession/URLSession.swift b/Sources/Prch/Session/URLSession/URLSession.swift index 00e2cb7..fd341fb 100644 --- a/Sources/Prch/Session/URLSession/URLSession.swift +++ b/Sources/Prch/Session/URLSession/URLSession.swift @@ -13,8 +13,8 @@ extension URLSession: Session { request: RequestType, withBaseURL baseURLComponents: URLComponents, withHeaders headers: [String: String], - authorizationManager: any AuthorizationManager, - usingEncoder encoder: any Coder + authorizationManager: any AuthorizationManager, + usingEncoder encoder: any Encoder ) async throws -> URLSession.Response where RequestType: ServiceCall { var componenents = baseURLComponents @@ -62,18 +62,9 @@ extension URLSession: Session { continuation.resume(with: result) } } - completed(result) - } - task.resume() - return task - } - - - public func request(_ request: URLRequest) async throws -> URLSessionResponse { - let tuple = try await self.data(for: request) - guard let response = URLSessionResponse(tuple) else { - throw RequestError.invalidResponse(tuple.1) - } - return response + #else + let tuple: (Data, URLResponse) = try await data(for: urlRequest) + return try Response(tuple) + #endif } } diff --git a/Sources/Prch/Session/URLSession/URLSessionResponse.swift b/Sources/Prch/Session/URLSession/URLSessionResponse.swift index d8b45cf..4f7b7e3 100644 --- a/Sources/Prch/Session/URLSession/URLSessionResponse.swift +++ b/Sources/Prch/Session/URLSession/URLSessionResponse.swift @@ -26,7 +26,6 @@ extension URLSession.Response { internal init(_ tuple: (Data, URLResponse)) throws { try self.init(urlResponse: tuple.1, data: tuple.0) } - internal init(urlResponse: URLResponse, data: Data) throws { guard let response = urlResponse as? HTTPURLResponse else { @@ -43,16 +42,7 @@ extension URLSession.Response { guard let urlResponse = urlResponse, let data = data else { return nil } - self.httpURLResponse = httpURLResponse - self.data = data - } - - -} -extension URLSessionResponse { - - internal init?(_ tuple: (Data, URLResponse)) { - self.init(urlResponse: tuple.1, data: tuple.0) + try self.init(urlResponse: urlResponse, data: data) } } diff --git a/Sources/Prch/StatusCodeProvider.swift b/Sources/Prch/StatusCodeProvider.swift deleted file mode 100644 index 0c9734a..0000000 --- a/Sources/Prch/StatusCodeProvider.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public protocol StatusCodeProvider { - static var statusCode: Int { get } -} diff --git a/Sources/Prch/SuccessModel.swift b/Sources/Prch/SuccessModel.swift deleted file mode 100644 index 6ba3782..0000000 --- a/Sources/Prch/SuccessModel.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -public typealias SuccessModel = Model & StatusCodeProvider diff --git a/Sources/PrchModel/API.swift b/Sources/PrchModel/API.swift new file mode 100644 index 0000000..183ce8a --- /dev/null +++ b/Sources/PrchModel/API.swift @@ -0,0 +1,10 @@ +import Foundation + +public protocol API { + associatedtype RequestDataType + associatedtype ResponseDataType + var baseURLComponents: URLComponents { get } + var headers: [String: String] { get } + var encoder: any Encoder { get } + var decoder: any Decoder { get } +} diff --git a/Sources/PrchModel/Array.swift b/Sources/PrchModel/Array.swift index 20944bc..048f8ca 100644 --- a/Sources/PrchModel/Array.swift +++ b/Sources/PrchModel/Array.swift @@ -1,9 +1,9 @@ extension Array: ContentDecodable where Element: ContentDecodable & Decodable, Element.DecodableType == Element { - public static func decode( - _ data: CoderType.DataType, - using coder: CoderType - ) throws -> [Element.DecodableType] where CoderType: Coder { + public static func decode( + _ data: DataType, + using coder: DecoderType + ) throws -> [Element.DecodableType] where DecoderType: Decoder { try coder.decode([Element.DecodableType].self, from: data) } diff --git a/Sources/PrchModel/Content.swift b/Sources/PrchModel/Content.swift index 2108abb..166cff2 100644 --- a/Sources/PrchModel/Content.swift +++ b/Sources/PrchModel/Content.swift @@ -1,50 +1 @@ -public enum EncodableValue { - case encodable(Encodable) - case empty -} - -public protocol ContentEncodable { - var encodable : EncodableValue { get } -} - -public protocol ContentDecodable { - associatedtype DecodableType : Decodable - static var decodable : DecodableType.Type? { get } - init(decoded : DecodableType?) throws -} - public typealias Content = ContentEncodable & ContentDecodable - -extension Encodable where Self : ContentEncodable { - public var encodable : EncodableValue { - return .encodable(self) - } -} -extension Decodable where Self : ContentDecodable, DecodableType == Self { - public static var decodable : Self.Type? { - return Self.self - } - public init(decoded : DecodableType?) throws { - guard let decoded = decoded else { - throw CoderError.missingDecoding - } - self = decoded - } -} - -extension Array : ContentDecodable where Element : ContentDecodable, Element.DecodableType == Element { - public static var decodable: Array.Type? { - return Self.self - } - - public init(decoded: Array?) throws { - guard let decoded = decoded else { - throw CoderError.missingData - } - self = decoded - } - - public typealias DecodableType = Array - - -} diff --git a/Sources/PrchModel/ContentDecodable.swift b/Sources/PrchModel/ContentDecodable.swift index 0420a10..057e324 100644 --- a/Sources/PrchModel/ContentDecodable.swift +++ b/Sources/PrchModel/ContentDecodable.swift @@ -2,7 +2,7 @@ public protocol ContentDecodable { associatedtype DecodableType static var decodable: DecodableType.Type { get } init(decoded: DecodableType) throws - static func decode( + static func decode( _ data: CoderType.DataType, using coder: CoderType ) throws -> DecodableType diff --git a/Sources/PrchModel/CustomServiceDecoding.swift b/Sources/PrchModel/CustomServiceDecoding.swift new file mode 100644 index 0000000..66303da --- /dev/null +++ b/Sources/PrchModel/CustomServiceDecoding.swift @@ -0,0 +1,5 @@ +@available(macOS 13, iOS 16, watchOS 9, tvOS 16, *) +public protocol CustomServiceDecoding { + associatedtype DataType + var decoder: any Decoder { get } +} diff --git a/Sources/PrchModel/CustomServiceEncoding.swift b/Sources/PrchModel/CustomServiceEncoding.swift new file mode 100644 index 0000000..98779e3 --- /dev/null +++ b/Sources/PrchModel/CustomServiceEncoding.swift @@ -0,0 +1,5 @@ +@available(macOS 13, iOS 16, watchOS 9, tvOS 16, *) +public protocol CustomServiceEncoding { + associatedtype DataType + var encoder: any Encoder { get } +} diff --git a/Sources/PrchModel/Decodable.swift b/Sources/PrchModel/Decodable.swift index 8d11075..b31f3f2 100644 --- a/Sources/PrchModel/Decodable.swift +++ b/Sources/PrchModel/Decodable.swift @@ -11,7 +11,7 @@ extension Decodable public static func decode( _ data: CoderType.DataType, using coder: CoderType - ) throws -> Self where CoderType: Coder { + ) throws -> Self where CoderType: Decoder { try coder.decode(Self.self, from: data) } } diff --git a/Sources/PrchModel/Coder.swift b/Sources/PrchModel/Decoder.swift similarity index 72% rename from Sources/PrchModel/Coder.swift rename to Sources/PrchModel/Decoder.swift index 6fece4b..91f95e9 100644 --- a/Sources/PrchModel/Coder.swift +++ b/Sources/PrchModel/Decoder.swift @@ -1,8 +1,6 @@ -public protocol Coder { +public protocol Decoder { associatedtype DataType - func encode(_ value: CodableType) throws -> DataType - func decode( _: CodableType.Type, from data: DataType @@ -10,7 +8,7 @@ public protocol Coder { throws -> CodableType } -extension Coder { +extension Decoder { public func decodeContent( _: CodableType.Type, from data: DataType diff --git a/Sources/PrchModel/Empty.swift b/Sources/PrchModel/Empty.swift index 005fbe8..39a5710 100644 --- a/Sources/PrchModel/Empty.swift +++ b/Sources/PrchModel/Empty.swift @@ -2,7 +2,7 @@ public struct Empty: ContentDecodable, ContentEncodable, Equatable { public static func decode( _: CoderType.DataType, using _: CoderType - ) throws where CoderType: Coder {} + ) throws where CoderType: Decoder {} public static var decodable: Void.Type { Void.self diff --git a/Sources/PrchModel/Encoder.swift b/Sources/PrchModel/Encoder.swift new file mode 100644 index 0000000..3c13e00 --- /dev/null +++ b/Sources/PrchModel/Encoder.swift @@ -0,0 +1,7 @@ +public protocol Encoder { + associatedtype DataType + + func encode( + _ value: CodableType + ) throws -> DataType +} diff --git a/Sources/PrchModel/JSONCoder.swift b/Sources/PrchModel/JSONCoder.swift deleted file mode 100644 index 8570504..0000000 --- a/Sources/PrchModel/JSONCoder.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation - -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif -public protocol LegacyCoder { - associatedtype DataType - - func encode(_ value: CodableType) throws -> DataType - - func decode(_: CodableType.Type, from data: DataType) - throws -> CodableType -} -public protocol Coder : LegacyCoder{ -} - -enum CoderError : Error { - case missingData - case missingDecoding -} - -public struct Empty : ContentDecodable, ContentEncodable, Decodable { - public typealias DecodableType = Empty - - public var encodable: EncodableValue { - return .empty - } - - static public var decodable: Empty? { - return nil - } - - public static let value = Empty() - - internal init () {} - - public init(decoded: Empty?) throws { - - } -} -public struct JSONCoder: Coder { - - - - - public typealias DataType = Data - - private let encoder: JSONEncoder - private let decoder: JSONDecoder - - public init(encoder: JSONEncoder, decoder: JSONDecoder) { - self.encoder = encoder - self.decoder = decoder - } - - public func encode( - _ value: CodableType - ) throws -> Data where CodableType: Encodable { - try encoder.encode(value) - } - - public func decode( - _ type: CodableType.Type, - from data: Data - ) throws -> CodableType where CodableType: Decodable { - try decoder.decode(type, from: data) - } -} diff --git a/Sources/PrchModel/JSONDecoder.swift b/Sources/PrchModel/JSONDecoder.swift new file mode 100644 index 0000000..b94a5cd --- /dev/null +++ b/Sources/PrchModel/JSONDecoder.swift @@ -0,0 +1,5 @@ +import Foundation + +extension JSONDecoder: Decoder { + public typealias DataType = Data +} diff --git a/Sources/PrchModel/JSONEncoder.swift b/Sources/PrchModel/JSONEncoder.swift new file mode 100644 index 0000000..d1b0144 --- /dev/null +++ b/Sources/PrchModel/JSONEncoder.swift @@ -0,0 +1,5 @@ +import Foundation + +extension JSONEncoder: Encoder { + public typealias DataType = Data +} diff --git a/Sources/PrchModel/RequestMethod.swift b/Sources/PrchModel/RequestMethod.swift index bb4f623..b197a3c 100644 --- a/Sources/PrchModel/RequestMethod.swift +++ b/Sources/PrchModel/RequestMethod.swift @@ -1,4 +1,4 @@ -public enum RequestMethod: String { +public enum RequestMethod: String, CaseIterable { case POST case GET case PUT diff --git a/Sources/PrchModel/ServiceCall.swift b/Sources/PrchModel/ServiceCall.swift index 633f592..2d81bf0 100644 --- a/Sources/PrchModel/ServiceCall.swift +++ b/Sources/PrchModel/ServiceCall.swift @@ -1,9 +1,9 @@ import Foundation -import PrchModel public protocol ServiceCall { associatedtype SuccessType: ContentDecodable associatedtype BodyType: ContentEncodable + associatedtype ServiceAPI var path: String { get } var parameters: [String: String] { get } var method: RequestMethod { get } @@ -14,11 +14,47 @@ public protocol ServiceCall { } extension ServiceCall { - public func isValidStatusCode(_ statusCode: Int) -> Bool { + public func isValidStatusCode( + _ statusCode: Int + ) -> Bool { statusCode / 100 == 2 } } +extension ServiceCall { + public func resolveEncoder( + with api: ServiceAPI + ) -> any Encoder + where ServiceAPI: API, ServiceAPI.RequestDataType == DataType { + if #available(macOS 13.0.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) { + if let custom = self as? any CustomServiceEncoding { + return custom.encoder + } else { + return api.encoder + } + } else { + return api.encoder + } + } +} + +extension ServiceCall { + public func resolveDecoder( + with api: ServiceAPI + ) -> any Decoder + where ServiceAPI: API, ServiceAPI.ResponseDataType == DataType { + if #available(macOS 13.0.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) { + if let custom = self as? any CustomServiceDecoding { + return custom.decoder + } else { + return api.decoder + } + } else { + return api.decoder + } + } +} + extension ServiceCall where BodyType == Empty { public var body: BodyType { .value diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index f9a6642..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest - -import PrchTests - -var tests = [XCTestCaseEntry]() -tests += PrchTests.__allTests() - -XCTMain(tests) diff --git a/Tests/PrchTests/PrchTests.swift b/Tests/PrchTests/PrchTests.swift index b524b0d..8f6bee8 100644 --- a/Tests/PrchTests/PrchTests.swift +++ b/Tests/PrchTests/PrchTests.swift @@ -1,5 +1,11 @@ -@testable import Prch import XCTest + final class PrchTests: XCTestCase { - func testPrch() {} + 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. + } } diff --git a/Tests/PrchTests/ServiceImplTests.swift b/Tests/PrchTests/ServiceImplTests.swift index 0f791de..b287f7d 100644 --- a/Tests/PrchTests/ServiceImplTests.swift +++ b/Tests/PrchTests/ServiceImplTests.swift @@ -20,7 +20,7 @@ extension Data { } } -class MockerCoder: Coder { +class MockerCoder: Decoder, Encoder { internal init(expectedSuccess: MockSessionSuccess) { self.expectedSuccess = expectedSuccess } @@ -55,19 +55,30 @@ struct MockSessionResponse: SessionResponse { let statusCode: Int } -struct MockCreds {} +struct MockCreds: SessionAuthorization { + var httpHeaders: [String: String] { + [:] + } +} class MockSession: Session { + func data(request: RequestType, withBaseURL _: URLComponents, withHeaders _: [String: String], authorizationManager _: any AuthorizationManager, usingEncoder _: any Encoder) async throws -> MockSessionResponse where RequestType: PrchModel.ServiceCall { + passedRequest = request + return MockSessionResponse(data: data, statusCode: statusCode) + } + + typealias RequestDataType = Data + + typealias AuthorizationType = SessionAuthorization + let statusCode: Int = .random(in: 100 ... 999) let data: Data = .random() var passedRequest: (any ServiceCall)? - func data(request: RequestType, withBaseURL _: URLComponents, withHeaders _: [String: String], authorization _: MockCreds?, usingEncoder _: any Coder) async throws -> MockSessionResponse where RequestType: Prch.ServiceCall { + func data(request: RequestType, withBaseURL _: URLComponents, withHeaders _: [String: String], authorization _: MockCreds?, usingEncoder _: any Encoder) async throws -> MockSessionResponse { passedRequest = request return MockSessionResponse(data: data, statusCode: statusCode) } - typealias RequestType = MockSessionRequest - typealias ResponseType = MockSessionResponse } @@ -79,14 +90,29 @@ struct MockBody: ContentEncodable, Codable, Equatable { let id: UUID } +struct MockAPI: API { + let baseURLComponents: URLComponents + + let headers: [String: String] + + let encoder: any Encoder + + let decoder: any Decoder + + typealias RequestDataType = Data + + typealias ResponseDataType = Data +} + struct MockSessionGenericRequest: ServiceCall, Equatable { - internal init(body: MockBody, path: String, parameters: [String: String], method: String, headers: [String: String], requiresCredentials: Bool) { + typealias ServiceAPI = MockAPI + + internal init(body: MockBody, path: String, parameters: [String: String], method: PrchModel.RequestMethod, headers: [String: String], requiresCredentials _: Bool) { self.body = body self.path = path self.parameters = parameters self.method = method self.headers = headers - self.requiresCredentials = requiresCredentials } typealias SuccessType = MockSessionSuccess @@ -99,11 +125,13 @@ struct MockSessionGenericRequest: ServiceCall, Equatable { var parameters: [String: String] - var method: String + var method: PrchModel.RequestMethod var headers: [String: String] - var requiresCredentials: Bool + static var requiresCredentials: Bool { + false + } func isValidStatusCode(_: Int) -> Bool { // self.passedStatusCode = statusCode @@ -111,20 +139,50 @@ struct MockSessionGenericRequest: ServiceCall, Equatable { } } +struct MockAuthenticationManager: AuthorizationManager { + let value: MockCreds? + func fetch() async throws -> SessionAuthorization? { + value + } + + typealias AuthorizationType = SessionAuthorization +} + +class MockService: Service { + internal init(api: MockAPI, session: MockSession, authorizationManager: any SessionAuthenticationManager) { + self.api = api + self.session = session + self.authorizationManager = authorizationManager + } + + internal convenience init(baseURLComponents: URLComponents, headers: [String: String], creds: MockCreds?, session: MockSession, coder: MockerCoder) { + let api = MockAPI(baseURLComponents: baseURLComponents, headers: headers, encoder: coder, decoder: coder) + let manager = MockAuthenticationManager(value: creds) + self.init(api: api, session: session, authorizationManager: manager) + } + + typealias SessionType = MockSession + typealias ServiceAPI = MockAPI + + var api: MockAPI + var session: MockSession + var authorizationManager: any SessionAuthenticationManager +} + final class ServiceImplTests: XCTestCase { func testExample() async throws { let successID = UUID() let session = MockSession() let coder = MockerCoder(expectedSuccess: .init(id: successID)) - let service = Service( + let service = MockService( baseURLComponents: .random(), - fetchAuthorization: { MockCreds() }, - session: session, headers: .random(withCount: 5), + creds: .init(), + session: session, coder: coder ) - let request = MockSessionGenericRequest(body: .init(id: .init()), path: UUID().uuidString, parameters: .random(withCount: 5), method: UUID().uuidString, headers: .random(withCount: 5), requiresCredentials: false) + let request = MockSessionGenericRequest(body: .init(id: .init()), path: UUID().uuidString, parameters: .random(withCount: 5), method: RequestMethod.allCases.randomElement()!, headers: .random(withCount: 5), requiresCredentials: false) let response = try await service.request(request) diff --git a/Tests/PrchTests/XCTestManifests.swift b/Tests/PrchTests/XCTestManifests.swift deleted file mode 100644 index 332b73c..0000000 --- a/Tests/PrchTests/XCTestManifests.swift +++ /dev/null @@ -1,18 +0,0 @@ -#if !canImport(ObjectiveC) - import XCTest - - extension PrchTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__PrchTests = [ - ("testPrch", testPrch) - ] - } - - public func __allTests() -> [XCTestCaseEntry] { - return [ - testCase(PrchTests.__allTests__PrchTests) - ] - } -#endif