Reputation:
I'm trying to validate user's email and password using two separate function calls.
Both functions return AnyPublisher publishers, and I use combineLatest to collect the returned values (each validate call returns the string it's validating) into a tuple.
Then I'm using flatMap to make a network request to sign the user up using the values returned by combineLatest, however the flatMap operator never gets called.
validator.validate(text: email, with: [.validEmail])
.combineLatest(validator.validate(text: password, with: [.notEmpty]))
.flatMap { credentials in
return self.userSessionRepository.signUp(email: credentials.0, password: credentials.1)
}
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print(error)
self.indicateErrorSigningIn(error)
case .finished:
self.goToSignInNavigator.navigateToOtp()
}
}, receiveValue: { _ in })
.store(in: &subscriptions)
signUp(email:password:) returns AnyPublisher
Here's the validator function:
public func validate(text: String, with rules: [Rule]) -> AnyPublisher<String, ErrorMessage> {
rules.publisher
.compactMap { $0.check(text) }
.setFailureType(to: ErrorMessage.self)
.flatMap {
Fail<Void, ErrorMessage>(error: ErrorMessage(title: "Error", message: $0.description))
}
.map { text }
.eraseToAnyPublisher()
}
And the signUp function:
public func signUp(email: String, password: String) -> AnyPublisher<Void, ErrorMessage> {
remoteAPI.signUp(email: email, password: password)
.flatMap(dataStore.save)
.mapError { error -> ErrorMessage in
return ErrorMessage(title: "Error", message: error.description)
}
.eraseToAnyPublisher()
}
It calls these two functions:
public func signUp(email: String, password: String) -> AnyPublisher<Confirmation, RemoteError> {
guard email == "[email protected]" else {
return Fail<Confirmation, RemoteError>(error: .invalidCredentials)
.eraseToAnyPublisher()
}
return Just(Confirmation(otp: "", nonce: "abcd"))
.setFailureType(to: RemoteError.self)
.eraseToAnyPublisher()
}
public func save(confirmation: Confirmation) -> AnyPublisher<Void, RemoteError> {
self.nonce = confirmation.nonce
return Empty().eraseToAnyPublisher()
}
I'm not sure what's wrong, though it's likely my not understanding Combine enough, as I've just started learning it recently.
Upvotes: 1
Views: 352
Reputation:
I have figured it out.
The problem was with the validate(text:with:) function.
In the event of an error, the function behaved correctly, but when there was no error the function wasn't emitting any value, and that's why flatMap or any other operator in the pipeline wasn't being invoked.
The reason it wasn't emitting any values boils down to how the check(_:) function, which is called in compactMap, works. It returns an optional string, which is an error message. But if there's no error, there's no string, and thus no value is emitted.
As a result, the call to .map { text } doesn't get evaluated, and the credential doesn't get returned.
I've changed the code to this and now the program behaves correctly:
public func validate(text: String, with rules: [Rule]) -> AnyPublisher<String, ErrorMessage> {
rules.publisher
.setFailureType(to: ErrorMessage.self)
.tryMap { rule -> String in
if let error = rule.check(text) {
throw ErrorMessage(title: "Error", message: error)
}
return text
}
.mapError { error -> ErrorMessage in
return error as! ErrorMessage
}
.eraseToAnyPublisher()
}
Upvotes: 1