Reputation: 5421
I'm working on a form-input library. My goal is to have a re-usable set of validators which can be applied to a set of form fields. I'm running into difficulty specialising my generic protocol. The full error from the code below is protocol 'FieldValidator' can only be used as a generic constraint because it has Self or associated type requirements
.
Complete playground-ready code:
import Foundation
protocol FieldValidator {
associatedtype InputType: Any
func validate(input value: InputType)
}
struct EmailValidator: FieldValidator {
func validate(input value: String) {}
}
enum Field {
case string(_: [FieldValidator])
case integer(_: [FieldValidator])
}
let emailField: Field = .string([EmailValidator()])
What I've tried
I understand that in the Field
enum I can't just throw in a FieldValidator
because it needs to know what InputType
of validator it requires. I expect that I need to tell it somehow, maybe something like this:
case string(_: [FieldValidator<String>])
case integer(_: [FieldValidator<Int>])
or this:
case string(_: [FieldValidator where InputType == String])
case integer(_: [FieldValidator where InputType == Int])
but these doesn't work. Is there a way to keep this kind of architecture?
Edit using struct
instead of enum
for field types:
struct StringField {
typealias InputType = String
let validators: [FieldValidator]
}
I still appear to have the same problem defining the set of validators (which must be provided when the Field is initialised): protocol 'FieldValidator' can only be used as a generic constraint because it has Self or associated type requirements
.
Upvotes: 0
Views: 159
Reputation: 535945
I suppose what I'm trying to do is provide a mechanism by which someone can define a Field, define what type of value it holds, and define a set of reusable Validators which will operate on that value and determine if it's valid
You might be after something like this; it's stupid but effective, especially if there are not very many field types in question:
protocol FieldValidator {
associatedtype T
func validate(input:T)
}
class StringValidator : FieldValidator {
func validate(input:String) { fatalError("must override me") }
}
class IntValidator : FieldValidator {
func validate(input:Int) { fatalError("must override me") }
}
class ActualStringValidator : StringValidator {
override func validate(input:String) { print(input)}
}
enum Field {
case string([StringValidator])
case int([IntValidator])
}
As you can see, I've simply used the class hierarchy to solve the problem (so that we don't have to do type erasure). In particular, it is now legal to say:
let f = Field.string([ActualStringValidator()])
Here's how to test it:
let f = Field.string([ActualStringValidator()])
if case Field.string(let arr) = f {
for thing in arr {
thing.validate(input:"howdy")
}
}
Upvotes: 1