Search code examples
swiftxcodeapollo

Convert my type to expected argument type


I'm using Apollo Client to autogenerate graphql types and queries in Xcode but have run into an issue I'm not sure how to best solve. TLDR is how can I allow one struct to be used as an argument for a function that accepts another type with the same structure?

Apollo generates a struct for me called "SizeInput" as follows:

struct SizeInput: GraphQLMapConvertible {
  public var graphQLMap: GraphQLMap
  public init(cm: Double) {
    graphQLMap = ["cm": cm]
  }
  public var cm: Double {
    get {
      return graphQLMap["cm"] as! Double
    }
    set {
      graphQLMap.updateValue(newValue, forKey: "cm")
    }
  }
}

(BTW - GraphQLMap is a typealias for [String : JSONEncodable?])

I created a Size struct for use in my code. I don't want to use the SizeInput in my code to keep the back-end separate from my app code so things don't break if the back-end or app changes.

struct Size {
  let cm: Double
}

I need to run an apollo query that takes SizeInput as an argument, which I want to populate from my Size variable populated from a picker. While I know I can create a new SizeInput from Size, I'd love to just pass Size into the query because they're essentially the same, just two different types. As of now, XCode obviously complains Cannot convert value of type 'Size' to expected argument type 'SizeInput'

The kicker is that I can't (don't want to) change the Apollo function because that's autogenerated. So my ideal solution is to typecast Size to SizeInput in some fashion, but XCode complains if I just try to do let sizeInput = size as? SizeInput where size is a Size.

Assuming I can't modify the function accepting the argument and I can't modify SizeInput because both are autogenerated, how can I modify Size so it can be used as a SizeInput argument?


Solution

  • Size and SizeInput according to your post are two different types and therefore can't be type aliased in the manner you are thinking.

    Swift's Declarations reference says the following about typealias:

    A type alias declaration introduces a named alias of an existing type into your program. [...] After a type alias is declared, the aliased name can be used instead of the existing type everywhere in your program. [...] Type aliases do not create new types; they simply allow a name to refer to an existing type.

    ... but that's not what you want. Size and SizeInput are similar contractually in that they have a cm: Double property, but are structurally different under the hood and can't be used interchangeably.

    You're going to need to make some trade-offs with whatever solution you choose. I see at least the following options.

    • You can write a layer that sits on top of your codegen GraphQL stuff that provides the interface you want (i.e. takes a Size) and use that. Then translate your Size to SizeInput. Codegen could be used.
    • You could provide some sort of type-level function of Size to SizeInput on the Size type itself and then use that at your GraphQL call-sites. Codegen could be used here too.
    extension Size {
       func asSizeInput() -> SizeInput {
            return SizeInput(cm: cm)
        }
    }
    
    • You could also at the call-site into your GraphQL just pass your Size instance's cm property into the constructor of SizeInput, then you're not writing any extra code. Here's some pseudo code:
    let size = Size(cm: 100)
    let query = SizeInputGraphQLQuery(sizeInput: size.cm)
    

    For my $, I'd opt for writing as little extra code as possible.

    YMMV