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

Question regarding schema generation with GraphQL-Interfaces #6

Closed
asherrecv opened this issue Jul 17, 2017 · 8 comments
Closed

Question regarding schema generation with GraphQL-Interfaces #6

asherrecv opened this issue Jul 17, 2017 · 8 comments
Labels

Comments

@asherrecv
Copy link

I have got Java types with SPQR-Annotations the following manner:

@GraphQLInterface(name = "IAnimal")
public interface IAnimal {
	@GraphQLQuery(name = "name")
	public String getName();
}
@GraphQLType(name = "IBat")
public interface IBat extends IAnimal {
	@GraphQLQuery(name = "wingSpan")
	public int getWingSpan();
}
@GraphQLType(name = "ICat")
public interface ICat extends IAnimal {
	@GraphQLQuery(name = "smart")
	public boolean isSmart();
}

With corresponding implementations of my interfaces which I have annotated with @GraphQLIgnore().

public class Service {
	@GraphQLQuery(name = "zoo")
	public List<IAnimal> getAnimals() {
		List<IAnimal> retval = new ArrayList<>();
		retval.add(new BatImpl());
		retval.add(new CatImpl());
		return retval;
	}
	
	@GraphQLQuery(name = "cat")
	public ICat getCat() {
		return new CatImpl();
	}

//      (if I comment the following method in everything works as expected)	
//	@GraphQLQuery(name = "bat")
//	public IBat getBat() {
//		return new BatImpl();
//	}

}

Having all this I create a schema with the GraphQLSchemaGenerator with

GraphQLSchema schemaFromAnnotated = new GraphQLSchemaGenerator()//
				.withBasePackage("test.graphql.model")//
				.withOperationsFromSingleton(service)//
				.generate();

Now, when I query { zoo { name, __typename } } the library throws a GraphQLException, telling me that it "Could not determine the exact type of IAnimal". However, when I add the getBat()-method to my Service class (and thus having every type of my schema referenced directly) -- everything works fine. Seemingly, the schema generator does not, at least when invoked with the default parameters, include all implemented interfaces automatically. The result of the query

{ __type(name: "IAnimal") { name, possibleTypes { name } } },

listing only ICat as possible type, adds to the suspicion.

Now I'd like to know if there's a way to tell the generator to also include derived types even if only a base class is referenced in a service class.

Thanks in advance.

@kaqqao
Copy link
Member

kaqqao commented Jul 17, 2017

This is a fairly involved setup, so I'll have to analyze this in more depth, but at the first glance it seems auto discovery would work for you.

Just add implementationAutoDiscovery = true to your @GraphQLInterface, i.e. make it @GraphQLInterface(name = "IAnimal", implementationAutoDiscovery = true). This is enough if the implementations are within the base package (test.graphql.model). If they're not, you can also specify the packages to scan via scanPackages property of @GraphQLInterface.

Does this satisfy your needs or did I misunderstand you?

@kaqqao
Copy link
Member

kaqqao commented Jul 17, 2017

Ah. I've just tested your scenario. With implementationAutoDiscovery it still fails but for a different reason: both BatImpl and IBat are discovered and it still doesn't know which one to choose. As a temporary work-around, you can explicitly set the scanPackages and keep the interfaces and the impls in separate packages, because the GraphQLIgnore annotation is not considered in this case.

I'll implement the support for GraphQLIgnore in this context in 0.9.2

Another option is to enable implementationAutoDiscovery but then implement your own TypeResolver and provide it via @GraphQLTypeResolver(CustomTypeResolver.class) on either IAnimal or the impl classes.

@kaqqao
Copy link
Member

kaqqao commented Jul 18, 2017

Yet a third option would be to implement your own TypeMapper (extending InterfaceMapper) that will take care of the custom logic of detecting and mapping the correct implementations. This is where I'll add the code to skip the implementations annotated with @GraphQLIgnore .

It was an important design goal to keep graphql-spqr insanely extensible and customizable, so if there's a feature you're missing, the chances are you can easily add it yourself.

@asherrecv
Copy link
Author

asherrecv commented Jul 18, 2017

Thanks for your effort! I tried out your second solution and it works. I kept Java-Implementations and Java-Interfaces in separate packages, enabled implementationAutoDiscovery on the IAnimal-GraphQL-Interface and additionally annotated it with @GraphQLTypeResolver(DummyTypeResolver.class) (where DummyTypeResolver.resolveType always returns null) and created the schema as I did before.

Now the only thing that raises a question mark, is that the automatically created _type_-field in the domain objects contains the actual Java-Type (e.g. BatImpl instead of IBat) and not the type defined in the GraphQL domain.

@kaqqao
Copy link
Member

kaqqao commented Jul 18, 2017

If you're keeping the interfaces and implementations in separate packages, you don't need the @GraphQLTypeResolver.

And as for the _type_ property, it should always be the concrete type that can be instantiated.
It is only needed in case an interface or an abstract class is used as an input type, because it would be impossible to deserialize it from JSON without the knowledge of the concrete type. So it is basically a hint to the deserializer. The only reason _type_ exists in output types is to help the client know the possible value.
It is safe to use, as the possible values of _type_ are whitelisted at startup, so the client can't attempt to inject an arbitrary class name.

In short, if you're not using interfaces/abstract types as input types, you don't care about the _type_ field.

@pmbdias
Copy link

pmbdias commented Jun 14, 2019

I have a problem similar to the one reported in this topic but I could not find a solution.
I have got Java types with SPQR-Annotations the following manner:

@GraphQLInterface(name = "IdentificationIf", implementationAutoDiscovery=true)
public interface IdentificationIf{
	@GraphQLQuery(name = "identification")
	public String getIdentification();	
}
@GraphQLType(name = "IdentificationCPF")
public class IdentificationCPF implements IdentificationIf{	
	private String identification;

	public IdentificationCPF(String identification){
		this.identification = identification;
	}

	@GraphQLQuery(name = "identification")
	public String getIdentification(){
		return this.identification;
	}	

	@GraphQLQuery(name = "type")
	public String getType() {
		return "CPF";
	}
}

@GraphQLType(name = "IdentificationCNPJ")
public class IdentificationCNPJ implements IdentificationIf{	
	private String identification;

	public IdentificationCNPJ(String identification){
		this.identification = identification;
	}

	@GraphQLQuery(name = "identification")
	public String getIdentification(){
		return this.identification;
	}

	@GraphQLQuery(name = "branch")
	public String getBranch() {
		String branch = "";
		if(identification != null) {
			branch = identification.substring(7, 12);
		}
		return branch;
	}	
}

@Service
public class ServiceIdentification {	
	@GraphQLQuery(name = "person")
	public Person getSolicitante() {
		Person solic = new Person();
		solic.setName("José");
		solic.setIdentification(new IdentificationCPF("88888888888"));
		return solic;
	}	

	@GraphQLQuery(name = "identificacaoCPF")
	public IdentificationIf getIdentificacaoCPF() {
		return new IdentificationCPF("12345678912");
	}	

	@GraphQLQuery(name = "identificacaoCNPJ")
	public IdentificationIf getIdentificacaoCNPJ() {
		return new IdentificationCNPJ("12345678912345");
	}	
}

When executing the query the error occurs:

{"query":"{person{name, ... on IdentificationCPF {identification, type}, ... on IdentificationCNPJ {identification, branch}}}"}
{errors=[{message=Validation error of type UnknownType: Unknown type IdentificationCPF, locations=[]}, {message=Validation error of type UnknownType: Unknown type IdentificationCNPJ, locations=[]}]}

I'd like to know how I should proceed to work with complex object queries that have interface type attribute.

Thanks in advance.

@kaqqao
Copy link
Member

kaqqao commented Jun 15, 2019

@pmbdias I don't see anything obviously wrong in your code... I'd suggest you place a breakpoint in e.g. GraphQLController and analyze the generated schema. It seems as if the implementation types are not automatically discovered for whatever reason. Maybe even place a breakpoint in InterfaceTypeMapper and what happens when it tries to scan for implementations.

@pmbdias
Copy link

pmbdias commented Jun 17, 2019

The difference is that the IdentificationCPF object is a concrete class and in the initial example of the post the ICat object is an interface.
The example has two interface levels, but in my case I only have one interface.
I put the breakpoint in GraphQLController and in the generated schema for the example of "zoo" the GraphQLObjectType is generated for ICat, but in my example the GraphQLObjectType does not generate only the InterfaceType.
e.g schemes:

catteste=GraphQLFieldDefinition{name='catteste', type=GraphQLObjectType{name='ICat', description='', fieldDefinitionsByName=[_type_, name, smart], interfaces=[GraphQLInterfaceType{name='IAnimal', description='', fieldDefinitionsByName=[_type_, name], typeResolver=io.leangen.graphql.generator.DelegatingTypeResolver@3f594bf}]}, arguments=[], dataFetcherFactory=graphql.schema.DataFetcherFactories$$Lambda$19748/431014167@1758940c, description='', deprecationReason='null', definition=null}

identificationCPF=GraphQLFieldDefinition{name='identificationCPF', type=GraphQLInterfaceType{name='IdentificationIf', description='', fieldDefinitionsByName=[_type_, formattedID, identification], typeResolver=io.leangen.graphql.generator.DelegatingTypeResolver@3f594bf}, arguments=[], dataFetcherFactory=graphql.schema.DataFetcherFactories$$Lambda$19748/431014167@33f28f68, description='', deprecationReason='null', definition=null}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants