Composing property wrappers in Swift with Vapor + Fluent

I've implemented a system to automatically generate Fluent's Migrations by reflecting 'registered' Models, saving the boilerplate of creating them manually. To achieve this, I must allow the specification of any DatabaseSchema.FieldConstraints directly in the Model. Since I'm unable to inherit from FieldProperty, etc. as they're marked as final, this system requires that I use nested property wrappers like so:

final class User: Model {
…
    @Constrained(.required)
    @Field(key: .userForename)
    var forename: String
…
}

Inside the implementation of the @Constrained property wrapper, I have the following code:

@propertyWrapper
class ConstrainedProperty<T, U>: AnyConstrainedProperty where T: Fields, U: AnyProperty {
    // MARK: - Properties

    // MARK: Property Wrapper

    var projectedValue: ConstrainedProperty<T, U> { self }

    var wrappedValue: U

    // MARK: Stored

    var constraints: [DatabaseSchema.FieldConstraint]

    // MARK: - Initialisers

    init(wrappedValue: U, _ constraints: DatabaseSchema.FieldConstraint...) {
        self.wrappedValue = wrappedValue
        self.constraints = constraints
    }
}

// MARK: - Fields

extension Fields {
    typealias Constrained<T> = ConstrainedProperty<Self, T> where T: Property
}

Since wrappedValue is Optional, wrappedValue never being initialised is not an issue and the code compiles just fine. However, if I want wrappedValue not to be Optional and for it to be provided a value when the property wrapper is initialised, the Swift Evolution proposal (SE-0258) states that I should use the following code:

var wrappedValue: T
var constraints: [DatabaseSchema.FieldConstraint]

init(wrappedValue: T, _ constraints: DatabaseSchema.FieldConstraint...) {
    self.wrappedValue = wrappedValue
    self.constraints = constraints
}

(T should be FieldProperty<Model, Value> where Model == Self and Value: Codable.)

Unfortunately, the issue is that Swift complains:

It doesn't appear that Swift's passing the value of the nested property wrapper to the wrappedValue parameter of the initialiser of the outer property wrapper automatically — I inferred from the proposal that it should; how else would property wrapper composition work :sweat_smile:?

1 Like

I have stumbled upon the exact same problem. Have you found a solution yet?