Code Monkey home page Code Monkey logo

Comments (13)

josemduarte avatar josemduarte commented on May 18, 2024 1

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.

kaqqao avatar kaqqao commented on May 18, 2024

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.

efenderbosch avatar efenderbosch commented on May 18, 2024

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.

efenderbosch avatar efenderbosch commented on May 18, 2024

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.

kaqqao avatar kaqqao commented on May 18, 2024

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.

efenderbosch avatar efenderbosch commented on May 18, 2024

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.

efenderbosch avatar efenderbosch commented on May 18, 2024

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.

efenderbosch avatar efenderbosch commented on May 18, 2024

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.

efenderbosch avatar efenderbosch commented on May 18, 2024

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.

kaqqao avatar kaqqao commented on May 18, 2024

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.

efenderbosch avatar efenderbosch commented on May 18, 2024

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.

kaqqao avatar kaqqao commented on May 18, 2024

Glad you sorted it out 👍

from graphql-spqr.

kaqqao avatar kaqqao commented on May 18, 2024

Cool! Glad you got it figured out. Thanks for sharing the solution :)

from graphql-spqr.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.