artemis icon indicating copy to clipboard operation
artemis copied to clipboard

Fragment on interface - generates not existing case

Open vasilich6107 opened this issue 4 years ago • 11 comments

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);
}
''';

vasilich6107 avatar May 25 '20 15:05 vasilich6107

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!

m-skolnick avatar May 28 '20 23:05 m-skolnick

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

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

vasilich6107 avatar May 29 '20 07:05 vasilich6107

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.

m-skolnick avatar May 29 '20 13:05 m-skolnick

Hmmm, I faced the same issue(

vasilich6107 avatar Jun 02 '20 08:06 vasilich6107

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

m-skolnick avatar Jun 04 '20 15:06 m-skolnick

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

vasilich6107 avatar Jun 11 '20 05:06 vasilich6107

@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.

m-skolnick avatar Jun 12 '20 02:06 m-skolnick

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

vasilich6107 avatar Jun 12 '20 05:06 vasilich6107

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!
}

m-skolnick avatar Jun 19 '20 14:06 m-skolnick

Full Schema and full query please

vasilich6107 avatar Jun 19 '20 17:06 vasilich6107

Is there any progress or workaround?

enex avatar Jul 10 '22 07:07 enex