comigor / artemis

Build dart types from GraphQL schemas and queries
MIT License
495 stars 119 forks source link

Fragment on interface - generates not existing case #151

Open vasilich6107 opened 4 years ago

vasilich6107 commented 4 years ago

In case of using

... on Node {
   ...NodeFrag
}

it generates case statement that refers to not existent class

case r'Node':
  return (this as Custom$Query$Node$Node).toJson();

Just put this code in test file

import 'package:artemis/generator/data.dart';
import 'package:test/test.dart';

import '../../helpers.dart';

void main() {
  group('On interfaces', () {
    test(
      'On interfaces',
      () async => testGenerator(
        query: query,
        schema: graphQLSchema,
        libraryDefinition: libraryDefinition,
        generatedFile: generatedFile,
      ),
    );
  });
}

const query = r'''
  query custom($id: ID!) {
    nodeById(id: $id) {
      ... on Node {
        ...NodeFrag
      }
      ... on User {
        ...UserFrag
      }
      ... on ChatMessage {
        message
        user {
          ...UserFrag
        }
      }
    }
  }

  fragment UserFrag on User {
    id
    username
  }

  fragment NodeFrag on Node {
    id
  }
''';

// https://graphql-code-generator.com/#live-demo
final String graphQLSchema = r'''
  scalar String
  scalar ID

  schema {
    query: Query
  }

  type Query {
    nodeById(id: ID!): Node
  }

  interface Node {
    id: ID!
  }

  type User implements Node {
    id: ID!
    username: String!
  }

  type ChatMessage implements Node {
    id: ID!
    message: String!
    user: User!
  }
''';

final LibraryDefinition libraryDefinition =
    LibraryDefinition(basename: r'query.graphql', queries: [
  QueryDefinition(
      queryName: r'custom',
      queryType: r'Custom$Query',
      classes: [
        ClassDefinition(
            name: r'Custom$Query$Node$User',
            extension: r'Custom$Query$Node',
            mixins: [r'UserFragMixin'],
            factoryPossibilities: {},
            typeNameField: r'__typename',
            isInput: false),
        ClassDefinition(
            name: r'Custom$Query$Node$ChatMessage$User',
            extension: r'Custom$Query$Node$ChatMessage',
            mixins: [r'UserFragMixin'],
            factoryPossibilities: {},
            typeNameField: r'__typename',
            isInput: false),
        ClassDefinition(
            name: r'Custom$Query$Node$ChatMessage',
            properties: [
              ClassProperty(
                  type: r'String',
                  name: r'message',
                  isNonNull: true,
                  isResolveType: false),
              ClassProperty(
                  type: r'Custom$Query$Node$ChatMessage$User',
                  name: r'user',
                  isNonNull: true,
                  isResolveType: false)
            ],
            extension: r'Custom$Query$Node',
            factoryPossibilities: {},
            typeNameField: r'__typename',
            isInput: false),
        ClassDefinition(
            name: r'Custom$Query$Node',
            properties: [
              ClassProperty(
                  type: r'String',
                  name: r'typeName',
                  annotations: [
                    r'override',
                    r'''JsonKey(name: '__typename')'''
                  ],
                  isNonNull: false,
                  isResolveType: true)
            ],
            factoryPossibilities: {
              r'Node': r'Custom$Query$Node$Node',
              r'User': r'Custom$Query$Node$User',
              r'ChatMessage': r'Custom$Query$Node$ChatMessage'
            },
            typeNameField: r'__typename',
            isInput: false),
        ClassDefinition(
            name: r'Custom$Query',
            properties: [
              ClassProperty(
                  type: r'Custom$Query$Node',
                  name: r'nodeById',
                  isNonNull: false,
                  isResolveType: false)
            ],
            factoryPossibilities: {},
            typeNameField: r'__typename',
            isInput: false),
        FragmentClassDefinition(name: r'UserFragMixin', properties: [
          ClassProperty(
              type: r'String',
              name: r'id',
              isNonNull: true,
              isResolveType: false),
          ClassProperty(
              type: r'String',
              name: r'username',
              isNonNull: true,
              isResolveType: false)
        ]),
        FragmentClassDefinition(name: r'NodeFragMixin', properties: [
          ClassProperty(
              type: r'String',
              name: r'id',
              isNonNull: true,
              isResolveType: false)
        ])
      ],
      inputs: [QueryInput(type: r'String', name: r'id', isNonNull: true)],
      generateHelpers: false,
      suffix: r'Query')
]);

const generatedFile = r'''// GENERATED CODE - DO NOT MODIFY BY HAND

import 'package:meta/meta.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:equatable/equatable.dart';
import 'package:gql/ast.dart';
part 'query.graphql.g.dart';

mixin UserFragMixin {
  String id;
  String username;
}
mixin NodeFragMixin {
  String id;
}

@JsonSerializable(explicitToJson: true)
class Custom$Query$Node$User extends Custom$Query$Node
    with EquatableMixin, UserFragMixin {
  Custom$Query$Node$User();

  factory Custom$Query$Node$User.fromJson(Map<String, dynamic> json) =>
      _$Custom$Query$Node$UserFromJson(json);

  @override
  List<Object> get props => [id, username];
  Map<String, dynamic> toJson() => _$Custom$Query$Node$UserToJson(this);
}

@JsonSerializable(explicitToJson: true)
class Custom$Query$Node$ChatMessage$User extends Custom$Query$Node$ChatMessage
    with EquatableMixin, UserFragMixin {
  Custom$Query$Node$ChatMessage$User();

  factory Custom$Query$Node$ChatMessage$User.fromJson(
          Map<String, dynamic> json) =>
      _$Custom$Query$Node$ChatMessage$UserFromJson(json);

  @override
  List<Object> get props => [id, username];
  Map<String, dynamic> toJson() =>
      _$Custom$Query$Node$ChatMessage$UserToJson(this);
}

@JsonSerializable(explicitToJson: true)
class Custom$Query$Node$ChatMessage extends Custom$Query$Node
    with EquatableMixin {
  Custom$Query$Node$ChatMessage();

  factory Custom$Query$Node$ChatMessage.fromJson(Map<String, dynamic> json) =>
      _$Custom$Query$Node$ChatMessageFromJson(json);

  String message;

  Custom$Query$Node$ChatMessage$User user;

  @override
  List<Object> get props => [message, user];
  Map<String, dynamic> toJson() => _$Custom$Query$Node$ChatMessageToJson(this);
}

@JsonSerializable(explicitToJson: true)
class Custom$Query$Node with EquatableMixin {
  Custom$Query$Node();

  factory Custom$Query$Node.fromJson(Map<String, dynamic> json) {
    switch (json['__typename'].toString()) {
      case r'Node':
        return Custom$Query$Node$Node.fromJson(json);
      case r'User':
        return Custom$Query$Node$User.fromJson(json);
      case r'ChatMessage':
        return Custom$Query$Node$ChatMessage.fromJson(json);
      default:
    }
    return _$Custom$Query$NodeFromJson(json);
  }

  @override
  @JsonKey(name: '__typename')
  String typeName;

  @override
  List<Object> get props => [typeName];
  Map<String, dynamic> toJson() {
    switch (typeName) {
      case r'Node':
        return (this as Custom$Query$Node$Node).toJson();
      case r'User':
        return (this as Custom$Query$Node$User).toJson();
      case r'ChatMessage':
        return (this as Custom$Query$Node$ChatMessage).toJson();
      default:
    }
    return _$Custom$Query$NodeToJson(this);
  }
}

@JsonSerializable(explicitToJson: true)
class Custom$Query with EquatableMixin {
  Custom$Query();

  factory Custom$Query.fromJson(Map<String, dynamic> json) =>
      _$Custom$QueryFromJson(json);

  Custom$Query$Node nodeById;

  @override
  List<Object> get props => [nodeById];
  Map<String, dynamic> toJson() => _$Custom$QueryToJson(this);
}
''';
m-skolnick commented 4 years ago

I am having this same issue.

Although for me, the fragment fails to build parse whether I have another fragment inside of the interface or not.

Both of these produce the same output:

fragment TextMessage on Text {
    id
    body
    audit {
        createdBy {
            entity {
                ... on User {
                    ...UserGql
                }
            }
        }
    }
}
fragment TextMessage on Text {
    id
    body
    audit {
        createdBy {
            entity {
                ... on User {
                    id
                    firstName
                    lastName
                }
            }
        }
    }
}

This is the very last bug that is keeping us from using Artemis for our project. All the other issues have been resolved in these last few beta builds. Great work guys!

vasilich6107 commented 4 years ago

Hi. Try to change your query in the next way and try again.

fragment TextMessage on Text {
    id
    body
    audit {
        createdBy {
            entity {
                ...UserGql
            }
        }
    }
}
m-skolnick commented 4 years ago

Hi. Try to change your query in the next way and try again.

fragment TextMessage on Text {
  id
  body
  audit {
      createdBy {
          entity {
              ...UserGql
          }
      }
  }
}

@vasilich6107 Hello.

When I try to build with the above code, I correctly get this error:

Exception: Field id was not found in GraphQL type Actor.
Make sure your query is correct and your schema is updated.

In the schema there are multiple possible interfaces in that entity, so although this error is pretty vague, it makes sense for the build to error out.

vasilich6107 commented 4 years ago

Hmmm, I faced the same issue(

m-skolnick commented 4 years ago

@vasilich6107 Were you able to find a work-around for this by any chance?

vasilich6107 commented 4 years ago

@m-Skolnick could you attach your schema and full query?

m-skolnick commented 4 years ago

@vasilich6107 I wish I could, but I can't share our schema. However, I would be happy to test and report back the results if that would be helpful.

vasilich6107 commented 4 years ago

Share only the core part and rename the fields))) So I can experiment with solution

m-skolnick commented 4 years ago

I'm not sure if this will be helpful, but here it is. It's just an interface that an object conforms to.

interface hasDisplayName {
  """A string used to display the player"""
  displayName: String!
}
type User implements hasDisplayName {
  """A string used to display the displayName"""
  displayName: String!
  firstName: String!
  lastName:String!
}
vasilich6107 commented 4 years ago

Full Schema and full query please

enex commented 2 years ago

Is there any progress or workaround?