leangen / graphql-spqr

Build a GraphQL service in seconds
Apache License 2.0
1.09k stars 179 forks source link

@GraphQLInterface annotation is not working for Input Type #403

Closed GaneshBChandgude closed 11 months ago

GaneshBChandgude commented 2 years ago

I am not sure this is issue or not. Might be my understanding/expectation is wrong. Request you to please verify my use case and error that I am facing.

Error Message:

graphql.schema.validation.InvalidSchemaException: invalid schema: "SchemaInput" must define one or more fields.

Java types with SPQR-Annotations the following manner:

public class Dataset {
     Schema schema;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "classType")
@GraphQLInterface(name = "Schema", implementationAutoDiscovery = true)
public interface Schema {
    String getClassType();
    Schema copy();
}

public class DatasetSchema implements Schema {
    List<Attribute> attributes = new ArrayList<>();

    public DatasetSchema copy() {
          return new DatasetSchema(this);
     }

    @JsonIgnore
    public String getClassType() {
        return DatasetSchema.class.getName();
    }
}

@GraphQLApi()
@org.springframework.stereotype.Service
public class Service {
    @GraphQLMutation(name = "createDataset")
    public Dataset createDataset(@GraphQLArgument(name = "dataset") Dataset dataset) {
        return Application.getDatasetService().createDataset(dataset);
    }
}

@org.springframework.context.annotation.Configuration
public class Configuration {

    @Bean
    public GraphQL graphQL(Service service) {
        GraphQLSchema schema = new GraphQLSchemaGenerator()
                .withResolverBuilders(new AnnotatedResolverBuilder())
                .withOperationsFromSingleton(service)
                .withValueMapperFactory(new JacksonValueMapperFactory())
                .generate();
        return GraphQL.newGraphQL(schema).build();
    }
}

I am getting above error on GraphQLSchemaGenerator.generate API

FYI...Above java types and SPQR annotations are working fine for output type. Working output type code snippet and query

@GraphQLQuery(name = "getFilteredDatasets")
public List<Dataset> getFilteredDatasets(@GraphQLArgument(name = "datasetFilter") DatasetFilter filter) {
    return Application.getDatasetService().getFilteredDatasets(filter);
}

Query:

{
  getFilteredDatasets(datasetFilter: {filterCriteria: {
    fetchSize: -1   
  }}){  
    schema{... on DatasetSchema {      
      attributes{
        name
        dataType
      }
    }
    }
  }
}
baohouse commented 1 year ago

You're talking about input polymorphism, which has a few RFCs out, let alone implemented support. https://github.com/graphql/graphql-spec/issues/627

kaqqao commented 11 months ago

While @baohouse is correct in the sense that GraphQL spec itself does not (yet) allow polymorphic input types, SPQR does (for the most part). You can enable this with generator.withAbstractInputTypeResolution() and SPQR will automatically add a discriminator input field called _type (by default, classType in your case) where needed. But. In your case, there is no field on type Schema that could possibly be deserialized in any of its concrete subtypes. How would Schema input value look like? There isn't a single valid value that could be provided.

If Schema instead looked something like this, for example:

 @GraphQLInterface(name = "Schema", implementationAutoDiscovery = true)
    public interface Schema {
        String getClassType();
        Schema copy();

        List<Attribute> getAttributes(); //This is deserializable in DatasetSchema, because there is a matching writable property called `attributes` (assuming Attribute is a valid input)
    }

then with .withAbstractInputTypeResolution(), SchemaInput would get mapped as:

input SchemaInput {
  attributes: [AttributeInput]
}

This is now a valid input type, and you could provide a value to a mutation as expected:

mutation CreateDataset { 
  createDataset(dataset: {schema: {attributes: [...]}}) {
    schema { 
      attributes {
        ...
      }
    }
  }
}

Notice how the _type field wasn't even present. This is because only a single concrete subtype of Schema exists, so no disambiguation was needed.

shree675 commented 5 months ago

@kaqqao following upon this, I have a very similar issue. You mentioned that the discriminator _type field is not required in case there is only a single implementation. But this is not working in my case below: These are my classes, all in the same package: Merge.java

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubType({
    @Type(value = MergeImpl1.class, name = "impl1")
})
@GraphQLInterface(name = "Merge", implementationAutoDiscovery = true)
public abstract class Merge {

    private String type;

    // getter and setter for "type"
}

MergeImpl1.java

public class MergeImpl1 extends Merge {

    private String request;

    public MergeImpl1() {
        setType("impl1");
    }

    // getter and setter for "request"
}

In the above classes, type field exists only to distinguish between the implementations.

The schema is now the following:

input MergeInput {
    type: String
}

Now I want to query on its implementation MergeImpl1:

query queries {
    myQuery(merge: {type: "impl1", request: "..."}) {
        id
        __typename
    }
}

On doing this, I get the following error: The variables input contains a field name 'request' that is not defined for input object type 'MergeInput'

For this, why doesn't type: "impl1" work? Should I be using _type instead of type in the query variable? In any case, since there is only a single implementation, specifying type should not be required. I might have misunderstood or might be missing something. Could you please help me in this?