How to create custom scalar type for ClosedRange when using Apollo for iOS?

I’ve implemented a Swift GraphQL web service using Vapor and Graphiti which has a custom schema for ClosedRange<Dobule> which is encoded into an array of two Doubles [5, 8].

On the client side I’m using Apollo for iOS and have tried to implement the custom scehma decoding by conforming ClosedRange<Dobule> to CustomScalarType but I get the error:

Conditional conformance of type ‘ClosedRange’ to protocol ‘CustomScalarType’ does not imply conformance to inherited protocol ‘AnyHashableConvertible’

// @generated
// This file was automatically generated and should not be edited.

import ApolloAPI
import Foundation

public extension ClientAPI {
  typealias DoubleClosedRange = ClosedRange<Double>
}

extension ClosedRange<Double>: CustomScalarType {
    public init(_jsonValue value: JSONValue) throws {
        guard let elements = value as? [Double] else {
            throw JSONDecodingError.couldNotConvert(value: value, to: [Double].self)
        }

        self = ClosedRange(uncheckedBounds: (lower: elements[0], upper: elements[1]))
    }

    public var _jsonValue: JSONValue {
        [lowerBound, upperBound]
    }
}

I’m not sure I really understand the error, I’ve created conformance for other Foundation types like URL in the same way without issues.

Hi @cameroncooke

Being generic ClosedRange itself has no unit type, it’s Bound that does instead. I believe it’s this that is tripping you up and you’ll need to provide conformance for all the required protocols narrowing the conformance to Double.

This builds but there might be some additional work needed in some of the empty extensions:

extension ClosedRange: AnyHashableConvertible where Bound == Double {}
extension ClosedRange: GraphQLOperationVariableValue where Bound == Double {}
extension ClosedRange: OutputTypeConvertible where Bound == Double {}
extension ClosedRange: AnyScalarType where Bound == Double {}
extension ClosedRange: CustomScalarType where Bound == Double {}

extension ClosedRange: JSONEncodable where Bound == Double {
  public var _jsonValue: JSONValue {
      [lowerBound, upperBound]
  }
}

extension ClosedRange: JSONDecodable where Bound == Double {
  public init(_jsonValue value: JSONValue) throws {
      guard let elements = value as? [Double] else {
          throw JSONDecodingError.couldNotConvert(value: value, to: [Double].self)
      }

      self = ClosedRange(uncheckedBounds: (lower: elements[0], upper: elements[1]))
  }
}

I’m not sure why extension ClosedRange: CustomScalarType where Bound == Double {..} doesn’t work. I assume it wants you to explicitly state the conformance?

Amazing, was starting to pull my hair out, thank you so much, that works!

I did try:

extension Double: CustomScalarType {
   ...
}

I thought that should work but maybe my understanding of conditional conformance was flawed.

We really need an open-source package of common Swift scalars conformances like URL, Range etc. I believe the JS community has a similar package.

1 Like