Search code examples
vaporvapor-fluent

Vapor 4: creating a protocol that contains a Fluent ParentProperty results in compiler errors


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?


Solution

  • 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.