Synchronizing two enums with overlapping cases

i have a schema key type defined like:

extension Record.Zone
{
    @frozen public
    enum CodingKey:String
    {
        case id = "_id"

        case package = "P"
        case version = "V"
        case refname = "G"
        case patch = "S"

        case min = "L"
        case max = "U"
    }
}

these keys describe the layout of records as stored in a database.

when performing database queries with an aggregation pipeline, i remove some of the fields, and add some additional fields:

extension SomeQuery.Output
{
    @frozen public
    enum CodingKey:String, CaseIterable
    {
        //  computed field
        case extensions = "e" 
        case matches = "a"
        case master = "m"

        //  original fields
        case package = "P"
        case version = "V"
        case refname = "G"
    }
}

but i’ve found it is hard to keep the original field names in sync with one another, or avoid accidentally using the same field name twice in the two sets of CodingKeys. (the keys must use single-letter names because BSON key names appear in each row, rather than once for the entire column.)

how to address this issue?

3 Likes

If you didn't have a backing type, I would suggest a case on each enum that has a third as an associated value.

But you have a backing type so… I only have an even more cumbersome suggestion of writing a macro that will splat the cases of one enum in to another. You could filter out the ones you don't want to move over. You'd still have a third enum type, though.

If you wait long enough… I'm sure I will write that second suggestion.

Remember there's nothing magic about the "backing type", it's just sugar for a RawRepresentable conformance. Maybe a macro could implement the RawRepresentable conformance for the three-enum approach.

2 Likes

You could have the derived enum produce members of the original.

extension SomeQuery.Output {
  enum CodingKey: ... {
    ...
    func recordField() -> Record.Zone.CodingKey? {
      switch self {
        case .package: return .package
        ...
      }
    }
  }
}

Then you test that the derived raw value is the same as the original's, and that no other derived raw value is the same as any original. That makes the invariant clear to any developer modifying the derived enum.

You can also apply and test any subset policy you can formulate (e.g., "derived all members pertaining to version coordinates used in package resolution"), ensuring new originals are added to the derived enum.