gql-dart / ferry

Stream-based strongly typed GraphQL client for Dart
https://ferrygraphql.com/
MIT License
593 stars 113 forks source link

Ferry cannot decompose fragment on TypeCondition #595

Open Masadow opened 3 months ago

Masadow commented 3 months ago

Assume the following schema :

  interface GenericValue {
    name: String
  }

  type TextValue implements GenericValue {
    name: String
    text: String
  }

  type NumberValue implements GenericValue {
    name: String
    number: Int
  }

  type Test {
    value: GenericValue
  }

  type CrashTest {
    a: Test
    b: Test
  }

  type Query {
    fixme: CrashTest
  }

And now, in your app, you query it like

fragment Text on TextValue {
  name
  text
}

fragment Number on NumberValue {
  name
  number
}

fragment CrashTestFragment on Test {
  value {
    ...Text
    ...Number
  }
}

query Test {
  fixme {
    a {
      ...CrashTestFragment
    }
    b {
      ...CrashTestFragment
    }
  }
}

Building the following will result in

[SEVERE] ferry_generator:graphql_builder on lib/src/views/questionnaires/questionnaires.graphql:

Bad state: No element
dart:collection                                          ListBase.firstWhere
package:gql_code_builder/src/operation/data.dart 457:8   _getFieldTypeNode
package:gql_code_builder/src/operation/data.dart 153:24  buildSelectionSetDataClasses.<fn>
dart:core                                                Iterable.toList
package:gql_code_builder/src/operation/data.dart 169:5   buildSelectionSetDataClasses
package:gql_code_builder/src/operation/data.dart 235:22  buildSelectionSetDataClasses.<fn>
dart:core                                                List.addAll
package:gql_code_builder/src/operation/data.dart 234:10  buildSelectionSetDataClasses
package:gql_code_builder/src/operation/data.dart 235:22  buildSelectionSetDataClasses.<fn>
dart:core                                                List.addAll
package:gql_code_builder/src/operation/data.dart 234:10  buildSelectionSetDataClasses
package:gql_code_builder/src/operation/data.dart 235:22  buildSelectionSetDataClasses.<fn>
dart:core                                                List.addAll
package:gql_code_builder/src/operation/data.dart 234:10  buildSelectionSetDataClasses
package:gql_code_builder/src/operation/data.dart 24:10   buildOperationDataClasses
package:gql_code_builder/data.dart 36:17                 buildDataLibrary.<fn>
dart:core                                                Iterable.toList
package:gql_code_builder/data.dart 46:8                  buildDataLibrary
package:ferry_generator/graphql_builder.dart 104:22      GraphqlBuilder.build

Until this is resolved, a workaround is to wrap your fragment with the type condition :

fragment CrashTestFragment on Test {
  value {
    ... on TextValue { ...Text }
    ... on NumberValue { ...Number }
  }
}

In case it's useful, source of graphql manifest : https://spec.graphql.org/October2021/#sec-Type-Conditions

knaeckeKami commented 3 months ago

the issue is here in buildSelectionSetDataClasses

    final typeDef = getTypeDefinitionNode(
        schemaSource.document,
        type,
      )!;
    final typeNode = _getFieldTypeNode(
        typeDef,
        node.name.value,
      );

we only get the type definition node for the type A the is queried, but the selection may come from a fragment that is on a subtype B of A So, when we lookup the queried field in A, we can't find it, as it is only in B.

We need to check the selections list for FragmentSpreadNode, and use the type from that FragmentSpreadNode instead when possible.

Edge cases to think about: