Comments (13)
I found this thread very useful. Thanks for sharing @efenderbosch and @kaqqao . My aim was very similar to @efenderbosch 's: I've got models without explicit graphql annotations and service methods that are annotated. I'll post my full final solution here in case this is useful for anyone:
import io.leangen.graphql.metadata.strategy.query.OperationNameGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.Introspector;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static com.google.common.base.CaseFormat.*;
/**
* A snake case name generator based on the ideas discussed in https://github.com/leangen/graphql-spqr/issues/32
*
*/
public class SnakeCaseOperationNameGenerator implements OperationNameGenerator {
private static final Logger logger = LoggerFactory.getLogger(SnakeCaseOperationNameGenerator.class);
@Override
public String generateQueryName(Method queryMethod, AnnotatedType declaringType, Object instance) {
return LOWER_CAMEL.to(LOWER_UNDERSCORE, getFieldNameFromGetter(queryMethod));
}
//when a field is exposed as a query
@Override
public String generateQueryName(Field queryField, AnnotatedType declaringType, Object instance) {
return LOWER_CAMEL.to(LOWER_UNDERSCORE, queryField.getName());
}
//when a method is exposed as a mutation
@Override
public String generateMutationName(Method mutationMethod, AnnotatedType declaringType, Object instance) {
return LOWER_CAMEL.to(LOWER_UNDERSCORE, getFieldNameFromGetter(mutationMethod));
}
@Override
public String generateSubscriptionName(Method subscriptionMethod, AnnotatedType declaringType, Object instance) {
return LOWER_CAMEL.to(LOWER_UNDERSCORE, getFieldNameFromGetter(subscriptionMethod));
}
/**
* Retrieve the field name from the getter Method (starting with "get" or "is")
* @param method
* @return
*/
private static String getFieldNameFromGetter(Method method) {
// implemented from ideas in : https://stackoverflow.com/questions/13192734/getting-a-property-field-name-using-getter-method-of-a-pojo-java-bean/13514566
String methodName = method.getName();
if (methodName.startsWith("get")) {
return Introspector.decapitalize(methodName.substring(3));
} else if (methodName.startsWith("is")) {
return Introspector.decapitalize(methodName.substring(2));
} else {
logger.warn("Method does not start with get or is, using the mehod name '{}' directly", methodName);
return methodName;
}
}
}
and then my graphql endpoint looks like this:
import javax.servlet.annotation.WebServlet;
import graphql.schema.GraphQLSchema;
import graphql.servlet.SimpleGraphQLServlet;
import io.leangen.graphql.GraphQLSchemaGenerator;
import io.leangen.graphql.metadata.strategy.query.BeanResolverBuilder;
import io.leangen.graphql.metadata.strategy.query.FilteredResolverBuilder;
import Query;
import SnakeCaseOperationNameGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends SimpleGraphQLServlet {
private static final Logger logger = LoggerFactory.getLogger(GraphQLEndpoint.class);
public GraphQLEndpoint() {
super(new Builder(buildSchema()));
}
private static GraphQLSchema buildSchema() {
logger.info("Initialising graphql schema");
// see https://github.com/leangen/graphql-spqr/issues/32 for the recipe I followed to snake case the schema
FilteredResolverBuilder customBean = new BeanResolverBuilder("org.path.to.my.models.package")
.withOperationNameGenerator(new SnakeCaseOperationNameGenerator());
Query query = new Query();
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withOperationsFromSingleton(query)
.withNestedResolverBuilders(customBean) //use the custom builders for nested operations
.generate();
return schema;
}
}
from graphql-spqr.
Yup, this is really easily done. The interesting interfaces are OperationNameGenerator
which controls the name generation for fields i.e. queries and mutations, and ResolverArgumentBuilder
which controls the process of mapping operation arguments, including generating their names.
Here's a quick example customizing both the operation and argument name generation, on all levels:
//Delegating generator configured to first try finding an explicit @GraphQLQuery or
//@GraphQLMutation annotation and use the name from there, if not found - use
//the custom name generator, which in your case could generate snake case names
OperationNameGenerator nameGenerator = new DelegatingOperationNameGenerator(new AnnotatedOperationNameGenerator(),
new OperationNameGenerator() {
//when a method is exposed as a query
@Override
public String generateQueryName(Method queryMethod, AnnotatedType declaringType, Object instance) {
return getSnakeCaseName(queryMethod);
}
//when a field is exposed as a query
@Override
public String generateQueryName(Field queryField, AnnotatedType declaringType, Object instance) {
return getSnakeCaseName(queryField);
}
//when a method is exposed as a mutation
@Override
public String generateMutationName(Method mutationMethod, AnnotatedType declaringType, Object instance) {
return getSnakeCaseName(mutationMethod);
}
});
AnnotatedArgumentBuilder argumentBuilder = new AnnotatedArgumentBuilder(new DefaultTypeTransformer(false, false)) {
@Override
protected String getArgumentName(Parameter parameter, AnnotatedType parameterType) {
return getSnakeCaseName(parameter);
}
};
//Customize the built-in resolver builders to use the implementations from above
FilteredResolverBuilder customAnnotated = new AnnotatedResolverBuilder()
.withOperationNameGenerator(nameGenerator)
.withResolverArgumentBuilder(argumentBuilder);
FilteredResolverBuilder customBean = new BeanResolverBuilder("your.base.package")
.withOperationNameGenerator(nameGenerator)
.withResolverArgumentBuilder(argumentBuilder);
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withOperationsFromSingleton(new Service())
.withResolverBuilders(customAnnotated) //use the custom builder for top-level operations
.withNestedResolverBuilders(customAnnotated, customBean) //use the custom builders for nested operations
.generate();
Notice how you can have different configurations for the top-level queries and mutations and the nested queries/fields.
You can also use the custom strategy per bean/class, e.g.
//only use the custom strategy for this bean
.withOperationsFromSingleton(new Service(), customAnnotated)
Instead of getSnakeCaseName
, you could also look for e.g. @JsonProperty
if you're already using Jackson, or whatever other strategy is appropriate for your case.
Does this help?
from graphql-spqr.
Getting this exception now:
java.lang.ExceptionInInitializerError
at com.company.graphql.GraphQLControllerTest.<clinit>(GraphQLControllerTest.java:101)
at sun.misc.Unsafe.ensureClassInitialized(Native Method)
at sun.reflect.UnsafeFieldAccessorFactory.newFieldAccessor(UnsafeFieldAccessorFactory.java:43)
at sun.reflect.ReflectionFactory.newFieldAccessor(ReflectionFactory.java:156)
at java.lang.reflect.Field.acquireFieldAccessor(Field.java:1088)
at java.lang.reflect.Field.getFieldAccessor(Field.java:1069)
at java.lang.reflect.Field.get(Field.java:393)
at org.junit.runners.model.FrameworkField.get(FrameworkField.java:73)
at org.junit.runners.model.TestClass.getAnnotatedFieldValues(TestClass.java:230)
at org.junit.runners.ParentRunner.classRules(ParentRunner.java:255)
at org.junit.runners.ParentRunner.withClassRules(ParentRunner.java:244)
at org.junit.runners.ParentRunner.classBlock(ParentRunner.java:194)
at org.junit.runners.ParentRunner.run(ParentRunner.java:362)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors:
1) Error injecting constructor, java.lang.IllegalArgumentException: Neither the method/field public java.lang.String com.company.models.coupon.CouponRule.getName() nor the declaring class are annotated with GraphQLQuery
at com.company.graphql.guice.GraphQLSchemaProvider.<init>(GraphQLSchemaProvider.java:29)
while locating com.company.graphql.guice.GraphQLSchemaProvider
at com.company.graphql.GraphQLTestModule.configure(GraphQLTestModule.java:31)
while locating graphql.schema.GraphQLSchema
for the 1st parameter of com.company.graphql.guice.GraphQLProvider.<init>(GraphQLProvider.java:17)
while locating com.company.graphql.guice.GraphQLProvider
at com.company.graphql.GraphQLTestModule.configure(GraphQLTestModule.java:32)
while locating graphql.GraphQL
for the 1st parameter of com.company.graphql.GraphQLExecutor.<init>(GraphQLExecutor.java:22)
at com.company.graphql.GraphQLTestModule.configure(GraphQLTestModule.java:51)
while locating com.company.graphql.GraphQLExecutor
1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:470)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:184)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
at com.google.inject.Guice.createInjector(Guice.java:99)
at com.google.inject.Guice.createInjector(Guice.java:73)
at com.google.inject.Guice.createInjector(Guice.java:62)
at com.company.graphql.GraphQLTestInjector.<clinit>(GraphQLTestInjector.java:16)
... 18 more
Caused by: java.lang.IllegalArgumentException: Neither the method/field public java.lang.String com.company.models.coupon.CouponRule.getName() nor the declaring class are annotated with GraphQLQuery
at io.leangen.graphql.metadata.strategy.query.AnnotatedOperationNameGenerator.queryName(AnnotatedOperationNameGenerator.java:34)
at io.leangen.graphql.metadata.strategy.query.AnnotatedOperationNameGenerator.generateQueryName(AnnotatedOperationNameGenerator.java:18)
at io.leangen.graphql.metadata.strategy.query.DelegatingOperationNameGenerator.lambda$generateQueryName$169(DelegatingOperationNameGenerator.java:25)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
at io.leangen.graphql.metadata.strategy.query.DelegatingOperationNameGenerator.generateName(DelegatingOperationNameGenerator.java:57)
at io.leangen.graphql.metadata.strategy.query.DelegatingOperationNameGenerator.generateQueryName(DelegatingOperationNameGenerator.java:25)
at io.leangen.graphql.metadata.strategy.query.PublicResolverBuilder.lambda$buildQueryResolvers$152(PublicResolverBuilder.java:52)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.metadata.strategy.query.PublicResolverBuilder.buildQueryResolvers(PublicResolverBuilder.java:59)
at io.leangen.graphql.metadata.strategy.query.PublicResolverBuilder.buildQueryResolvers(PublicResolverBuilder.java:35)
at io.leangen.graphql.generator.OperationRepository.lambda$buildQueryResolvers$42(OperationRepository.java:88)
at io.leangen.graphql.generator.OperationRepository.lambda$null$44(OperationRepository.java:100)
at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:267)
at java.util.Iterator.forEachRemaining(Iterator.java:116)
at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270)
at java.util.Collections$2.tryAdvance(Collections.java:4717)
at java.util.Collections$2.forEachRemaining(Collections.java:4725)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.generator.OperationRepository.buildResolvers(OperationRepository.java:102)
at io.leangen.graphql.generator.OperationRepository.buildQueryResolvers(OperationRepository.java:87)
at io.leangen.graphql.generator.OperationRepository.buildNestedQueries(OperationRepository.java:83)
at io.leangen.graphql.generator.OperationRepository.getNestedQueries(OperationRepository.java:59)
at io.leangen.graphql.generator.OperationRepository.getChildQueries(OperationRepository.java:65)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.getFields(ObjectTypeMapper.java:80)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.toGraphQLType(ObjectTypeMapper.java:37)
at io.leangen.graphql.generator.mapping.common.ObjectTypeMapper.toGraphQLType(ObjectTypeMapper.java:29)
at io.leangen.graphql.generator.mapping.common.CachingMapper.graphQLType(CachingMapper.java:28)
at io.leangen.graphql.generator.mapping.common.AbstractionCollectingMapper.toGraphQLType(AbstractionCollectingMapper.java:28)
at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:150)
at io.leangen.graphql.generator.OperationMapper.toGraphQLOperation(OperationMapper.java:122)
at io.leangen.graphql.generator.OperationMapper.lambda$generateQueries$20(OperationMapper.java:82)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.generator.OperationMapper.generateQueries(OperationMapper.java:83)
at io.leangen.graphql.generator.OperationMapper.<init>(OperationMapper.java:67)
at io.leangen.graphql.GraphQLSchemaGenerator.generate(GraphQLSchemaGenerator.java:692)
at com.company.graphql.guice.GraphQLSchemaProvider.<init>(GraphQLSchemaProvider.java:60)
at com.company.graphql.guice.GraphQLSchemaProvider$$FastClassByGuice$$11cad6b4.newInstance(<generated>)
at com.google.inject.internal.DefaultConstructionProxyFactory$FastClassProxy.newInstance(DefaultConstructionProxyFactory.java:89)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:111)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:61)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.SingleParameterInjector.inject(SingleParameterInjector.java:38)
at com.google.inject.internal.SingleParameterInjector.getAll(SingleParameterInjector.java:62)
at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:110)
at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:90)
at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:268)
at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:194)
at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41)
at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:205)
at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:199)
at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1085)
at com.google.inject.internal.InternalInjectorCreator.loadEagerSingletons(InternalInjectorCreator.java:199)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:180)
... 23 more
from graphql-spqr.
It seems like DelegatingOperationNameGenerator isn't catching the exception from AnnotatedOperationNameGenerator.
edit: Our models are not annotated with any sort of GraphQL annotations. This worked fine for camelCasedAttributes. The classes we are passing to withOperationsFromSingleton
are annotated with @GraphQLQuery
, @GraphQLMutation
and @GraphQLArgument
.
from graphql-spqr.
Yup, it's common to not have any annotations on the model.
Knowing that, you can drop the DelegatingOperationNameGenerator
and just register a custom one that makes snake case names, then use with e.g. BeanResolverBuilder
, and register that using withNestedResolverBuilders
(that one will be used for the model).
//create snakeCaseNameGenerator and snakeCaseArgumentBuilder as above
ResolverBuilder customBean = new BeanResolverBuilder("your.base.package")
.withOperationNameGenerator(snakeCaseNameGenerator)
.withResolverArgumentBuilder(snakeCaseArgumentBuilder); //drop this if you don't need to customize argument names
GraphQLSchema schema = new GraphQLSchemaGenerator()
.withOperationsFromSingleton(new Service())
.withNestedResolverBuilders(customBean)
.generate();
That should take care of it.
from graphql-spqr.
Ok, inbound snake_cased parameters are working. As are queries that return an integer or other primitives. However queries that return an object or an array of objects are still throwing validation errors, even for attributes like "id" that are the same in camelCase and snake_case.
This service method:
@GraphQLQuery(name = "shopify_tags")
public List<ShopifyTag> getByApplicationIdAndEntityType(
@GraphQLArgument(name = "application_id") String applicationId,
@GraphQLArgument(name = "entity_type") String entityType) {
return repo.getShopifyTagList(applicationId, EntityType.valueOf(entityType));
}
With this query: { shopify_tags (application_id: "blah-blah-blah") { id, application_id, entity_type, name } }
Returns:
{"errors":[{"validation_error_type":"FieldUndefined","message":"Validation error of type FieldUndefined: Field id is undefined","locations":[{"line":1,"column":75}],"error_type":"ValidationError"},{"validation_error_type":"FieldUndefined","message":"Validation error of type FieldUndefined: Field application_id is undefined","locations":[{"line":1,"column":79}],"error_type":"ValidationError"},{"validation_error_type":"FieldUndefined","message":"Validation error of type FieldUndefined: Field entity_type is undefined","locations":[{"line":1,"column":95}],"error_type":"ValidationError"},{"validation_error_type":"FieldUndefined","message":"Validation error of type FieldUndefined: Field name is undefined","locations":[{"line":1,"column":108}],"error_type":"ValidationError"}],"extensions":null}
from graphql-spqr.
Here's how the schema is being created:
AnnotatedArgumentBuilder argumentBuilder = new AnnotatedArgumentBuilder() {
@Override
protected String getArgumentName(Parameter parameter, AnnotatedType parameterType) {
return LOWER_CAMEL.to(LOWER_UNDERSCORE, parameter.getName());
}
};
FilteredResolverBuilder customBean = new BeanResolverBuilder("com.company")
.withOperationNameGenerator(new SnakeCaseOperationNameGenerator())
.withResolverArgumentBuilder(argumentBuilder);
GraphQLSchemaGenerator generator = new GraphQLSchemaGenerator();
services.forEach(generator::withOperationsFromSingleton);
generator.withNestedResolverBuilders(customBean);
generator.withValueMapperFactory(abstractTypes -> new JacksonValueMapper(MAPPER));
generator.withScalarMappingStrategy(new MapScalarStrategy());
schema = generator.generate();
I've set breakpoints in AnnotatedArgumentBuilder
but they are never triggered. Breakpoints in SnakeCaseOperationNameGenerator
are triggered, but they are for methods that aren't queries on the models.
from graphql-spqr.
So simply updating the @GraphQLQuery
and @GraphQLArgument
annotations to use snake_case seems sufficient to accept those parameters without any custom code , but the "projection" or returned fields still expects camelCased attributes. What does the introspection to build the list of attributes that can be returned in the result of a query/mutation?
from graphql-spqr.
I'm getting really close.
FilteredResolverBuilder customBean = new BeanResolverBuilder("com.company")
.withOperationNameGenerator(new SnakeCaseOperationNameGenerator());
GraphQLSchemaGenerator generator = new GraphQLSchemaGenerator();
services.forEach(generator::withOperationsFromSingleton);
generator.withNestedResolverBuilders(customBean);
generator.withValueMapperFactory(abstractTypes -> new JacksonValueMapper(MAPPER));
generator.withScalarMappingStrategy(new MapScalarStrategy());
schema = generator.generate();
and the important part of SnakeCaseOperationNameGenerator
:
@Override
public String generateQueryName(Method queryMethod, AnnotatedType declaringType, Object instance) {
GraphQLQuery annotation = queryMethod.getAnnotation(GraphQLQuery.class);
if (annotation != null) {
return annotation.name();
}
return LOWER_CAMEL.to(LOWER_UNDERSCORE, getFieldNameFromGetter(queryMethod));
}
from graphql-spqr.
Sure, setting annotation explicitly will always work, but I was under an impression this was something you didn't want to do. That's why I was suggesting configuring it from the outside.
What's still not working for you?
Can you give me an example class that gets mapped incorrectly?
from graphql-spqr.
Everything is working now. I had some minor tests to cleanup.
The models are not annotated, but the service methods are. Actually it seems like this:
@Override
public String generateQueryName(Method queryMethod, AnnotatedType declaringType, Object instance) {
return LOWER_CAMEL.to(LOWER_UNDERSCORE, getFieldNameFromGetter(queryMethod));
}
is sufficient now that SnakeCaseOperationNameGenerator
is only used for withNestedResolverBuilders
from graphql-spqr.
Glad you sorted it out 👍
from graphql-spqr.
Cool! Glad you got it figured out. Thanks for sharing the solution :)
from graphql-spqr.
Related Issues (20)
- Setup GitHub Pages for documentation
- Apply bean validation annotations only if an appropriate group is enabled [Breaking]
- How to use executionResult data HOT 2
- Failing tests - ComplexityTest class
- Explicitly register Java classes to the GraphQL schema. HOT 4
- How to map a java class to GraphQL interface without using @GraphQLInterface annotation? HOT 5
- Best way to error handling HOT 1
- Help in Handling Exceptions with SPQR HOT 1
- Question: Extending an InputObject type and handle it in SPQR HOT 3
- SDL Printing Fails on Schema Directives with Arguments HOT 4
- Spring 3.2: Cannot resolve parameter names for constructor public io.leangen.graphql.spqr.spring.web.dto.GraphQLRequest(java.lang.String,java.lang.String,java.lang.String,java.util.Map) HOT 2
- Compile parameters to support Spring Boot 3.2.1 (Spring Framework 6.1) HOT 1
- null values sent in response json HOT 1
- Schema Generation Regression in 0.12.4 HOT 1
- Polymorphism type on input
- Scalars containing an Instant cannot be serialized over subscription
- Schema generation at compile time?
- BUG: The DelegatingTypeResolver breaks when updating the GraphQLSchema using a Visitor HOT 1
- Feature Request: Add support for GraphQLOutputType to the SchemaTransformer HOT 39
- Make a public OperationMapper#createResolver overload to help with creating custom field resolvers
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-spqr.