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

addition of assertSuccess() and assertFailure() on EventLoopFuture #2417

Merged

Conversation

dkz2
Copy link
Contributor

@dkz2 dkz2 commented May 3, 2023

Added assertSuccess, assertFailure, preconditionSuccess and preconditionFailure methods to EventLoopFuture for safer and more explicit error handling.

Motivation:

Enhance developer experience by providing a clear way to handle expected success or failure of futures.

Closes #2359.

Modifications:

Implemented assertSuccess, assertFailure, preconditionSuccess and preconditionFailure for EventLoopFuture.
Added corresponding test cases to verify behavior of the new methods.

Result:

Developers will now have access to assertSuccess, assertFailure, preconditionSuccess and preconditionFailure methods onEventLoopFuture.

@Lukasa Lukasa added the semver/minor Adds new public API. label May 3, 2023
Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

Thanks for this! I am inclined to want to say that these methods do slightly too much. Having the callback as an argument muddys the water about their actual semantic. If these methods instead behave like a regular future function (where they return a new future) then we can allow them to compose with the rest of the future handling functions, which might be nicer.

@weissi I'm also curious whether you think assertion is sufficient, or whether we need precondition variants too.

@weissi
Copy link
Member

weissi commented May 3, 2023

Thanks for this! I am inclined to want to say that these methods do slightly too much. Having the callback as an argument muddys the water about their actual semantic. If these methods instead behave like a regular future function (where they return a new future) then we can allow them to compose with the rest of the future handling functions, which might be nicer.

Agreed!

@weissi I'm also curious whether you think assertion is sufficient, or whether we need precondition variants too.

I'd think we should have both precondition* and assert* variants

@dkz2 dkz2 force-pushed the dkz2-EventLoopFuture-assertSuccess-assertFailure branch 2 times, most recently from 37fe5c5 to ac4ea51 Compare May 3, 2023 15:06
@dkz2
Copy link
Contributor Author

dkz2 commented May 3, 2023

Thanks for this! I am inclined to want to say that these methods do slightly too much. Having the callback as an argument muddys the water about their actual semantic. If these methods instead behave like a regular future function (where they return a new future) then we can allow them to compose with the rest of the future handling functions, which might be nicer.

Agreed!

@weissi I'm also curious whether you think assertion is sufficient, or whether we need precondition variants too.

I'd think we should have both precondition* and assert* variants

Added precondition* variants, also removed all unneeded requirements.

case .success(let value):
promise.succeed(value)
case .failure(let error):
assertionFailure("Expected success, but got failure: \(error)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we pass file and line through from the line that calls assertSuccess? That'll make the crash more generally useful.

Copy link
Member

Choose a reason for hiding this comment

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

oh sorry, you already commented with that. Please ignore my extra SPAM 🙈

self.whenComplete { result in
switch result {
case .success(let value):
assertionFailure("Expected failure, but got success: \(value)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Same note here on file and line.

///
/// If the original future fails, it triggers a precondition failure, causing a runtime error during development.
@inlinable
public func ensureSuccess() -> EventLoopFuture<Value> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think using precondition instead of ensure on these two methods is better, as it fits with our existing prior art.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ran into an issue where preconditionFailure as the method name conflicts with the global preconditionFailure function in Swift, i could rename it to be something like preconditionOnSuccess potentially?

Copy link
Contributor

Choose a reason for hiding this comment

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

You can disambiguate by using Swift.preconditionFailure(...) to refer to the global one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good making the changes!

case .success(let value):
promise.succeed(value)
case .failure(let error):
preconditionFailure("Expected success, but got failure: \(error)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we pass file and line through?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes to all!

self.whenComplete { result in
switch result {
case .success(let value):
preconditionFailure("Expected failure, but got success: \(value)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we pass file and line through?

/// If the original future fails, it triggers an assertion failure, causing a runtime error during development.
@inlinable
public func assertSuccess() -> EventLoopFuture<Value> {
let promise = self.eventLoop.makePromise(of: Value.self)
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to create a promise in any of these functions; you can use always instead:

return self.always { result in 
    switch result { 
    case .success:
        ()
    case .failure(let error):
        assertionFailure("Expected success, but got failure: \(error)")    
    }
}

Copy link
Member

Choose a reason for hiding this comment

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

Indeed, but we should pass file and line to assertionFailure such that it shows where the user put .assertX and not always this line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh okay i see no need to create a new promise / or complete it bc always can take care of that for us, working on the changes. Thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changes commited!

///
/// If the original future fails, it triggers a precondition failure, causing a runtime error during development.
@inlinable
public func ensureSuccess() -> EventLoopFuture<Value> {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can disambiguate by using Swift.preconditionFailure(...) to refer to the global one.

///
/// If the original future fails, it triggers an assertion failure, causing a runtime error during development.
@inlinable
public func assertSuccess() -> EventLoopFuture<Value> {
Copy link
Member

Choose a reason for hiding this comment

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

here and in the other functions we should take file: ... = #file, line = ... #line and pass that to the (assertion/precondition)Failure functions

Copy link
Member

Choose a reason for hiding this comment

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

please use #fileID and not #file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done to all!

Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

Nice one, this LGTM.

@Lukasa
Copy link
Contributor

Lukasa commented May 4, 2023

@swift-server-bot test this please

@dnadoba dnadoba self-requested a review May 4, 2023 15:21
Copy link
Member

@dnadoba dnadoba left a comment

Choose a reason for hiding this comment

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

Thanks for taking care of this!

@dkz2
Copy link
Contributor Author

dkz2 commented May 4, 2023

Thank you all for the help and guidance @weissi @Lukasa @glbrntt @dnadoba, very much appreciated!

@Lukasa Lukasa merged commit 7f4d10b into apple:main May 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EventLoopFuture should have an assertSuccess() (and maybe assertFailure() methods)
5 participants