I have a whole bunch of Fluent (Vapor 4) models, all which have a parent field like this:
final class Location: Model, Content {
// .. bunch of other properties
@Parent(key: FieldKeys.campaign)
var campaign: Campaign
}
Now, I want to make a protocol that can be applied to all these models, like this:
protocol HasCampaignId: Model {
var _campaign: ParentProperty<Self, Campaign> { get }
func belongsToCampaign(_ campaignID: Campaign.IDValue) throws -> Self
}
extension HasCampaignId {
func belongsToCampaign(_ campaignID: Campaign.IDValue) throws -> Self {
if self._campaign.id != campaignID {
throw Abort(.forbidden)
}
return self
}
}
Just a convenient little function I can apply to any instance of a model that HasCampaignId
, that's the idea. Sadly this doesn't compile, I get the error Property '_campaign' must be declared internal because it matches a requirement in internal protocol 'HasCampaignId'
. I can make the protocol public, but then I just get another error: Property '_campaign' must be as accessible as its enclosing type because it matches a requirement in protocol 'HasCampaignId'
.
I could change the protocol like this:
protocol HasCampaignId: Model {
var campaign: Campaign { get }
func belongsToCampaign(_ campaignID: Campaign.IDValue) throws -> Self
}
extension HasCampaignId {
func belongsToCampaign(_ campaignID: Campaign.IDValue) throws -> Self {
if self.campaign.id != campaignID {
throw Abort(.forbidden)
}
return self
}
}
But that requires me to load the campaign relation, which is usually not what I want - otherwise it crashes with a fatal error: Fatal error: Parent relation not eager loaded, use $ prefix to access: Parent<Loot, Campaign>(key: campaign_id)
.
The campaign
property within my protocol can't have a property wrapper applied either.
So how can I have a protocol that requires a ParentProperty
field? How can I solve the compiler errors?
As of Swift 5.4 and 5.5 - you can't. Property wrappers in protocols have been pitched but they're not implemented and it's impossible for enforce. There was a lot of effort to try and work around these restrictions in Fluent but it's not currently possible to do what you're trying.