Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api): add collection type casting in swift 5.7 #3602

Merged
merged 3 commits into from
Apr 10, 2024

Conversation

5d
Copy link
Member

@5d 5d commented Apr 9, 2024

Issue #

Description

Swift Collection type casting is introduced in Swift 5.8. As our library is still supporting Swift 5.7, adding backward compatibility support for collection casting.

General Checklist

  • Added new tests to cover change, if needed
  • Build succeeds with all target using Swift Package Manager
  • All unit tests pass
  • All integration tests pass
  • Security oriented best practices and standards are followed (e.g. using input sanitization, principle of least privilege, etc)
  • Documentation update for the change if required
  • PR title conforms to conventional commit style
  • New or updated tests include Given When Then inline code documentation and are named accordingly testThing_condition_expectation()
  • If breaking change, documentation/changelog update with migration instructions

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 9, 2024 23:12 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 00:42 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 00:42 — with GitHub Actions Inactive
@5d 5d requested a review from phantumcode April 10, 2024 00:45
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d temporarily deployed to IntegrationTest April 10, 2024 01:18 — with GitHub Actions Inactive
@5d 5d marked this pull request as ready for review April 10, 2024 01:35
@5d 5d requested a review from a team as a code owner April 10, 2024 01:35
Copy link
Contributor

@lawmicha lawmicha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor refactoring comment, let me know if you need another approval if adopting it

Comment on lines +381 to +405
#if swift(<5.8)
if let errors = errors.cast(to: AppSyncRealTimeRequest.Error.self) {
let hasAuthorizationError = errors.contains(where: { $0 == .unauthorized})
return APIError.operationError(
errorDescription(hasAuthorizationError),
"",
errors.first
)
} else if let errors = errors.cast(to: GraphQLError.self) {
let hasAuthorizationError = errors.map(\.extensions)
.compactMap { $0.flatMap { $0["errorType"]?.stringValue } }
.contains(where: { AppSyncErrorType($0) == .unauthorized })
return APIError.operationError(
errorDescription(hasAuthorizationError),
"",
GraphQLResponseError<R>.error(errors)
)
} else {
return APIError.operationError(
errorDescription(),
"",
errors.first
)
}
#else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like you can probably reduce this once more

    let hasAuthorizationError: Bool
    let underlyingError: Error?
#if swift(<5.8)
    if let errors = errors.cast(to: AppSyncRealTimeRequest.Error.self) {
        hasAuthorizationError = errors.contains(where: { $0 == .unauthorized})
        underlyingError = errors.first
    } else if let errors = errors.cast(to: GraphQLError.self) {
        hasAuthorizationError = errors.map(\.extensions)
            .compactMap { $0.flatMap { $0["errorType"]?.stringValue } }
            .contains(where: { AppSyncErrorType($0) == .unauthorized })
        underlyingError = GraphQLResponseError<R>.error(errors)
    } else {
        hasAuthorizationError = false
        underlyingError = errors.first
    }
#else
    if let errors = errors as? [AppSyncRealTimeRequest.Error] {
        hasAuthorizationError = errors.contains(where: { $0 == .unauthorized})
        underlyingError = errors.first
    } else if let errors = errors as? [GraphQLError] {
        hasAuthorizationError = errors.map(\.extensions)
            .compactMap { $0.flatMap { $0["errorType"]?.stringValue } }
            .contains(where: { AppSyncErrorType($0) == .unauthorized })
        underlyingError = errors.first
    } else {
        hasAuthorizationError = false
        underlyingError = errors.first
    }
#endif
    return APIError.operationError(
        errorDescription(hasAuthorizationError),
        "",
        underlyingError
    )

Or even further, just use cast everywhere. This would avoid having to duplicate business logic while maintaining the Collection type casting in one place. Once we support 5.8 and up, then we can replace usages of cast with as

func cast<T>(to type: T.Type) -> [T]? {
        #if swift(<5.8)
        self.reduce([]) { partialResult, ele in
            if let partialResult, let ele = ele as? T {
                return partialResult + [ele]
            }
            return nil
        }
        #else
        return self as? [T]
        #endif
    }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of having the compiler directive in the cast function instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including the directive in the cast func will make unit testing harder. Once we upgrade the Swift tool version to 5.8, removing the code within the annotation will become straightforward.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it make unit testing harder? We don't need to unit-test that we support both 5.7 and 5.8

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chatted with @5d, the CI set up will cause cast to be unit tested against Swift 5.9 so it won't reach the code path. We'd have to refactor the unit test to test against something like:

func cast<T>(to type: T.Type) -> [T]? {
        #if swift(<5.8)
        return castSwift5_7() // update unit tests to test this function instead
        #else
        return self as? [T]
        #endif
    }

We to upgrade to min 5.8 soon so as Di mentioned it will be easier to remove the code later in the current PR

@5d 5d merged commit 50e001f into main Apr 10, 2024
135 checks passed
@5d 5d deleted the 5d/collection-type-casting-swift5.7 branch April 10, 2024 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants