Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LocalCacheMutation with an enum field fails with Invalid type in JSON write #2775

Closed
dabby-wombo opened this issue Jan 12, 2023 · 0 comments · Fixed by #2778
Closed

LocalCacheMutation with an enum field fails with Invalid type in JSON write #2775

dabby-wombo opened this issue Jan 12, 2023 · 0 comments · Fixed by #2778
Assignees
Labels
bug Generally incorrect behavior

Comments

@dabby-wombo
Copy link

dabby-wombo commented Jan 12, 2023

Summary

executing a LocalCacheMutation with an enum field causes the the ReadWriteTransaction to fail with an:

  • Invalid type in JSON write for SQLiteNormalizedCache
  • ApolloAPI.JSONDecodingError.couldNotConvert(value: AnyHashable(ApolloAPI.GraphQLEnum<SomeEnum>.case(someCase)), to: Swift.String)) for InMemoryNormalizedCache

The issue seems to spawn from the fact that on cache read, the enum gets created via it's rawValue accurately, but on write, in the case of the SQLiteNormalizedCache the JSONSerialization fails to serialize the data, as it receives an Enum type to serialize instead of the raw value of the corresponding enum case.

Adding an extra case to the SQLiteSerialization.serialize(fieldValue:) seems to fix this issue for the SQLiteNormalizedCache:

private static func serialize(fieldValue: Record.Value) throws -> Any {
    switch fieldValue {
    ...
    case let value as JSONEncodable:
        return value._jsonValue
    default:
      ...
    }
  }

But I'm not too sure the consequences of this particular change, and it also doesn't fix it for the inMemoryCache either.

Version

apollo-ios SDK version: 1.0.5

Steps to reproduce the behavior

I created a failing unit test in the ReadWriteFromStoreTests as follows:

For simplicity, I did a noop in the actual ReadWriteTransaction so as to make the issue in question clearer

func test_updateCacheMutation_enumFields_identityUpdates() throws {
    // given
    struct GivenSelectionSet: MockMutableRootSelectionSet {
      public var __data: DataDict = DataDict([:], variables: nil)
      init(data: DataDict) { __data = data }
      
      static var __selections: [Selection] { [
        .field("hero", Hero.self)
      ]}
      
      var hero: Hero {
        get { __data["hero"] }
        set { __data["hero"] = newValue }
      }
      
      enum HeroType: String, EnumType {
        case droid
      }
      
      struct Hero: MockMutableRootSelectionSet {
        public var __data: DataDict = DataDict([:], variables: nil)
        init(data: DataDict) { __data = data }
        
        static var __selections: [Selection] { [
          .field("type", GraphQLEnum<HeroType>.self)
        ]}
        
        var type: GraphQLEnum<HeroType> {
          get { __data["type"] }
          set { __data["type"] = newValue }
        }
      }
    }
    
    let cacheMutation = MockLocalCacheMutation<GivenSelectionSet>()
    
    mergeRecordsIntoCache([
      "QUERY_ROOT": ["hero": CacheReference("QUERY_ROOT.hero")],
      "QUERY_ROOT.hero": ["__typename": "Droid", "type": "droid"]
    ])
    
    runActivity("update mutation") { _ in
      let updateCompletedExpectation = expectation(description: "Update completed")
      
      store.withinReadWriteTransaction({ transaction in
        try transaction.update(cacheMutation) { data in
          // noop
        }
      }, completion: { result in
        defer { updateCompletedExpectation.fulfill() }
        XCTAssertSuccessResult(result)
      })
      
      self.wait(for: [updateCompletedExpectation], timeout: Self.defaultWaitTimeout)
    }
    
    let query = MockQuery<GivenSelectionSet>()
    
    loadFromStore(operation: query) { result in
      try XCTAssertSuccessResult(result) { graphQLResult in
        XCTAssertEqual(graphQLResult.source, .cache)
        XCTAssertNil(graphQLResult.errors)
        
        let data = try XCTUnwrap(graphQLResult.data)
        XCTAssertEqual(data.hero.type, .case(.droid))
      }
    }
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Generally incorrect behavior
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants