ferry icon indicating copy to clipboard operation
ferry copied to clipboard

Fragments not working with interfaces

Open letsar opened this issue 1 year ago • 13 comments

When I create a Fragment with a type which is an interface, or has fields which are interfaces, the generated code does not work at runtime.

For example let's have this schema:

interface Book {
  title: String!
  author: String!
}

type Textbook implements Book {
  title: String!
  author: String!
  courses: [String!]!
}

type ColoringBook implements Book {
  title: String!
  author: String!
  colors: [String!]!
}

type Query {
  books: [Book!]!
}

And this query:

fragment BookFragment on Book {
  title
  ... on Textbook {
      courses
  }
  ... on ColoringBook {
      colors
  }
}

query GetBooks {
  books {
    ...BookFragment
  }
}

These parameters are set on build.yaml:

  when_extensions:
    when: true
    maybeWhen: true

When we generate the code, we can see that nothing implements GBookFragment__asTextbook, yet we have this kind of generted code:

extension GBookFragmentWhenExtension on GBookFragment {
  _T when<_T>({
    required _T Function(GBookFragment__asTextbook) textbook,
    required _T Function(GBookFragment__asColoringBook) coloringBook,
    required _T Function() orElse,
  }) {
    switch (G__typename) {
      case 'Textbook':
        return textbook((this as GBookFragment__asTextbook));
      case 'ColoringBook':
        return coloringBook((this as GBookFragment__asColoringBook));
      default:
        return orElse();
    }
  }
}

Since nothing implements GBookFragment__asTextbook, the cast (this as GBookFragment__asTextbook) will fail. Is there a workaround for that?

letsar avatar Sep 02 '24 09:09 letsar

There's actually a deeper bug, the subtype-specific data does not even end up in the models

 final response = GGetBooksData.fromJson({
      "books": [
        {
          "__typename": "Textbook",
          "title": "Textbook",
          "courses": ["Math", "Science"]
        },
        {
          "__typename": "ColoringBook",
          "title": "ColoringBook",
          "colors": ["Red", "Blue"]
        }
      ]
    });
final GBookFragment textbook = response!.books[0]; // is of type _$GGetBooksData_books, with no data from the type conditions 

knaeckeKami avatar Sep 02 '24 11:09 knaeckeKami

@knaeckeKami have you an idea on how we can fix this issue? Is it an issue from ferry or from another package?

letsar avatar Sep 20 '24 15:09 letsar

It's an issue in the code generation in gql_code_builder. I didn't have time to look into this bug specifically yet. It might be an issue in the mergeSelections() or buildInlineFragmentClasses() function.

knaeckeKami avatar Sep 20 '24 15:09 knaeckeKami

FWIW this seems to only happen when fragments are nested. So as a workaround you could try to flatten the query.

knaeckeKami avatar Sep 20 '24 16:09 knaeckeKami

@nhannah Pretty sure this is the issue you were talking about 👀

btrautmann avatar Oct 02 '24 18:10 btrautmann

Hi, I had some time to look into the issue. I don't know what is really causing the issue, but I came up with what should like the data.gql.dart file : In the class GGetBooksData, instead of BuiltList<GGetBooksData_books> get books we should have BuiltList<GBookFragmentData> get books. The class GGetBooksData_books does not seem to be useful in this case. Now I need to see how to change this 😅

letsar avatar Nov 08 '24 16:11 letsar

Cool! I also looked a bit into it already.

I think there is something what with the mergeSelections function that doesn't handle nested fragments with type conditions properly

knaeckeKami avatar Nov 08 '24 17:11 knaeckeKami

@bestdan this is the issue we are seeing today

nhannah avatar Jan 09 '25 19:01 nhannah

@letsar This issue should now be resolved on the master branch of gql_code_builder. I'd appreciate your feedback!

knaeckeKami avatar Apr 14 '25 10:04 knaeckeKami

Hi @knaeckeKami, I had finally the time to test this solution a little more. In a simple case it seems to work, but in a more complex one I have code generation issues.

Fore example let's take this schema

interface Author {
  displayName: String!
}

type Person implements Author {
  firstName: String!
  lastName: String!
  displayName: String!
}

type Company implements Author {
  name: String!
  displayName: String!
}

interface Book {
  title: String!
  author: Author!
}

type Textbook implements Book {
  title: String!
  author: Author!
  courses: [String!]!
}

type ColoringBook implements Book {
  title: String!
  author: Author!
  colors: [String!]!
}

type Query {
  books: [Book!]!
}

and this query

fragment AuthorFragment on Author {
  displayName
  ... on Person {
      firstName
      lastName
  }
  ... on Company {
      name
  }
}

fragment BookFragment on Book {
  author {
    ...AuthorFragment
  }
  title
  ... on Textbook {
      courses
  }
  ... on ColoringBook {
      colors
  }
}

query GetBooks {
  books {
    ...BookFragment
  }
}

In the *.data.gql.dart there are some unknown implements generated (GBookFragment__asCompany and GBookFragment__asPerson).

In my project I have another issue, but I need to create a simple repro first before sharing with you.

letsar avatar Jun 12 '25 14:06 letsar

@knaeckeKami have you an insight on how to fix the late issue? If you have in mind a workaround it can work too 😅 . We have a lot of interfaces at multiple levels in our graph and I would love to know how to not have to duplicate a lot of code.

letsar avatar Oct 13 '25 15:10 letsar

Sorry, I do not have a workaround or fix at the moment. I think there is a pretty fundamental issue in the gql_code_builder, and fixing this would be a greater change - I do not have time to do this at the moment.

knaeckeKami avatar Oct 14 '25 21:10 knaeckeKami

Thank you for your response @knaeckeKami. I understand this can take a lot of time.

letsar avatar Oct 15 '25 08:10 letsar