tobygriffin
tobygriffin

Reputation: 5421

Getting around "Protocol X can only be used as a generic constraint"

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

Answers (1)

matt
matt

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

Related Questions