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
Throwing initializer #4
Comments
Given that these validations are constraints important enough to be elevated into the type system, my gut says using And as @radex notes, if you still want to use failable-initializer style, |
This sounds like a good idea to me. I like that we can preserve the easy case with a I just prototyped this in a playground and came up with the following
And this initializer implementation:
For my example this will result in the following default error message:
I'm testing this together with the logical operators from #3. I'm providing the wrapper value and the Validator metatype as part of the error to enable more complex error handling (and potentially overriding |
It would be cool to know exactly which validators failed when using aggregation validators like And/Or/Not. Therefore validators should also be able to throw errors to pass validation failures up to the root of the validation tree. Unfortunately this results in more complex validators. However, using protocol extensions, we are able to make throwing validators optional: struct ValidatorError: ErrorType, CustomStringConvertible {
let wrapperValue: Any
let validator: Any.Type
let errors: [ErrorType]
var description: String {
return "Value: \(wrapperValue) <\(wrapperValue.dynamicType)>, failed validation of Validator: \(validator), errors: \(errors)"
}
}
public protocol Validator {
typealias WrappedType
static func validate(value: WrappedType) -> Bool
static func validate(value: WrappedType) throws
}
extension Validator {
public static func validate(value: WrappedType) throws {
guard validate(value) else {
throw ValidatorError(wrapperValue: value, validator: self, errors: [])
}
}
} Now all leaf validators continue implementing extension And {
public static func validated(value: V1.WrappedType) throws {
let _: Void = try V1.validate(value)
let _: Void = try V2.validate(value)
}
}
extension Or {
public static func validated(value: V1.WrappedType) throws {
do { let _: Void = try V1.validate(value) } catch let error1 {
do { let _: Void = try V2.validate(value) } catch let error2 {
throw ValidatorError(wrapperValue: value, validator: self, errors: [error1, error2])
}
}
}
}
extension Not {
public static func validated(value: V1.WrappedType) throws {
do { let _:Void = try V1.validate(value) } catch { return }
throw ValidatorError(wrapperValue: value, validator: self, errors: [])
}
} |
I'm a little bit content about adding throwing I think this library should be mostly utilized in places where we are fairly certain that a validation will pass. I thought of the validation step more of a confirmation of an already inherent type rather than a validation that will likely fail. With this use case in mind I think we will almost never be able to recover from an error in any useful manner. By allowing to throw arbitrary errors from the I personally would handle complex validation outside of A very abstract of a signup process would look like this:
Validation of individual Strings (to match PW requirements, email address requirements, etc.) would happen outside of TL;DR; I think of P.S.: While writing this, I realized that I used a lot of string validation examples in my tests and the docs. But I might be entirely wrong, so I'm looking forward to seeing where a discussion takes us 😃 P.P.S: It seems like we currently have three different options to implement this: 1.Error for Any Type of Validation Failure - as suggested by my first comment) |
@tomquist There's also some additional complexity that I believed is unaddressed right now? Depending on the type of composite validator, the validator might pass even if one of the validators throws an error (e.g. using |
My initial intention when designing my proposal was not to be able to use validators for user input validation and automated recovery (which is also an interesting use case probably better handeled by a dedicated framework). I thought of it to be more like a debugging tool in case validators fail when you don't expect them to. But you are right, for this simple use case the proposal may be somewhat over-engineered. Here are my comments to the three options:
This may fit best to the initial intention of this library to be a tiny type confirmation library
I don't think this is overly useful. E.g. when using the
This is pretty much what I tried to implement. However, I had to move the |
I may suggest changing
to
which results in messages like
Moving on, my 2¢: Broadly, and admittedly without experimenting for more than a few minutes, @tomquist's proposal is interesting but feels too complex for a tiny pseudo-dependent-types library.
I can see the value here in theory, even when we don't expect validation errors to be recoverable. But overall I agree with the "1.Error for Any Type of Validation Failure" goal, unless we can achieve "Error that Specifies which Validation Failed, but doesn't allow the Validations to throw custom errors (they can only return a Hope that makes sense. edit: and, it's unclear to me precisely how |
That makes sense to me. From my perspective, what I care about is not really having an error caught so I can deal with it. Rather, I just want the failed conversions logged to the console and otherwise treat them as failed assertions. Aside: In my apps, I have a helper called
What I would like is to be able to convert from |
Thanks a lot for your input! I've added a PR based on our discussion: #7 I've found a way to keep the failable initializer around, which I'm pretty happy with. Let me know what you think! |
Closed by #7 |
A good suggestion from https://twitter.com/DeFrenZ/status/703521308550762496:
Instead of having the Validated initializer return nil if the constraint isn't met, you could throw a generic error. If you don't care about the error, you could still ignore it using
try?
, but you could also easily log it to the console when it's not expected (but not fatal). And you'd have an error like "A value of Foo didn't pass BarValidator" without having to type it yourself.The text was updated successfully, but these errors were encountered: