├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── graphql-core ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── merapar │ │ │ └── graphql │ │ │ ├── GraphQlAutoConfiguration.java │ │ │ ├── GraphQlFields.java │ │ │ ├── GraphQlProperties.java │ │ │ ├── Scalars.java │ │ │ ├── base │ │ │ ├── GraphQlFieldsHelper.java │ │ │ └── TypedValueMap.java │ │ │ ├── controller │ │ │ ├── GraphQlController.java │ │ │ └── GraphQlControllerImpl.java │ │ │ ├── executor │ │ │ ├── GraphQlExecutor.java │ │ │ ├── GraphQlExecutorImpl.java │ │ │ └── GraphQlExecutorProperties.java │ │ │ └── schema │ │ │ ├── GraphQlSchemaBuilder.java │ │ │ └── GraphQlSchemaBuilderImpl.java │ └── resources │ │ └── META-INF │ │ └── spring.factories │ └── test │ └── java │ └── com │ └── merapar │ └── graphql │ ├── GraphQlPropertiesTest.java │ ├── GraphQlPropertiesTestConfiguration.java │ ├── ScalarsTest.java │ ├── base │ └── TypedValueMapTest.java │ ├── controller │ ├── GraphQlControllerTest.java │ ├── GraphQlControllerTestConfiguration.java │ └── data │ │ ├── User.java │ │ ├── UserDataFetcher.java │ │ └── UserFields.java │ ├── executor │ ├── GraphQlExecutorImplTest.java │ ├── GraphQlExecutorImplTestConfiguration.java │ ├── GraphQlExecutorPropertiesTest.java │ └── GraphQlExecutorPropertiesTestConfiguration.java │ └── schema │ └── GraphQlSchemaBuilderImplTest.java ├── graphql-sample ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── merapar │ │ │ └── graphql │ │ │ └── sample │ │ │ ├── Application.java │ │ │ ├── CustomGraphQlExecutorImpl.java │ │ │ ├── dataFetchers │ │ │ ├── RoleDataFetcher.java │ │ │ └── UserDataFetcher.java │ │ │ ├── domain │ │ │ ├── Role.java │ │ │ └── User.java │ │ │ └── fields │ │ │ ├── HelloWorldFields.java │ │ │ ├── RoleFields.java │ │ │ └── UserFields.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── merapar │ └── graphql │ └── sample │ ├── ApplicationTest.java │ └── fields │ ├── RoleFieldsTest.java │ ├── RoleFieldsTestConfiguration.java │ ├── UserFieldsTest.java │ └── UserFieldsTestConfiguration.java ├── pom.xml └── src └── main └── bin └── bintray.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | *.iml 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: oraclejdk8 4 | 5 | cache: 6 | directories: 7 | - $HOME/.m2 8 | 9 | install: mvn install -DskipTests=true -B -V 10 | 11 | after_success: 12 | - bash <(curl -s https://codecov.io/bash) 13 | 14 | deploy: 15 | provider: bintray 16 | skip_cleanup: true 17 | file: target/bin/bintray.json 18 | user: $BINTRAY_API_USER 19 | key: $BINTRAY_API_KEY -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at jan.fockaert@merapar.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 45 | version 1.3.0, available at 46 | [http://contributor-covenant.org/version/1/3/0/][version] 47 | 48 | [homepage]: http://contributor-covenant.org 49 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Merapar Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Join the chat at https://gitter.im/merapar/graphql-spring-boot-starter](https://badges.gitter.im/merapar/graphql-spring-boot-starter.svg)](https://gitter.im/merapar/graphql-spring-boot-starter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | [![Build Status](https://api.travis-ci.org/merapar/graphql-spring-boot-starter.svg?branch=master)](https://travis-ci.org/merapar/graphql-spring-boot-starter) 3 | [![Code coverage](https://codecov.io/gh/merapar/graphql-spring-boot-starter/branch/master/graph/badge.svg)](https://codecov.io/gh/merapar/graphql-spring-boot-starter) 4 | [![Latest Release](https://maven-badges.herokuapp.com/maven-central/com.merapar/graphql-spring-boot-starter/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.merapar/graphql-spring-boot-starter/) 5 | [![Dev version](https://api.bintray.com/packages/merapar/maven/graphql-spring-boot-starter/images/download.svg)](https://bintray.com/merapar/maven/graphql-spring-boot-starter/_latestVersion) 6 | 7 | # GraphQL Spring boot starter 8 | 9 | This is a Spring boot starter project for the [GraphQL Java](https://github.com/graphql-java/graphql-java) project. 10 | 11 | 12 | # Table of Contents 13 | 14 | - [Overview](#overview) 15 | - [Getting started](#getting-started) 16 | - [Versioning](#versioning) 17 | - [Code of Conduct](#code-of-conduct) 18 | - [Contributions](#contributions) 19 | - [Acknowledgment](#acknowledgment) 20 | - [License](#license) 21 | 22 | 23 | ### Overview 24 | 25 | The implementation is based on Spring boot starter web project that will expose the GraphQL endpoint as rest controller. 26 | 27 | It takes care of exposing a rest endpoint with all configured graphQL fields automatically. 28 | 29 | The library aims for real-life usage in production with the ease of Spring Boot. 30 | 31 | 32 | ### Getting started 33 | 34 | Check out the following documentation on [using spring boot starter](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter) project. 35 | 36 | By adding GraphQL Spring boot starter as maven dependency on the application a @Controller will be created pointing to the configured request mapping with default "/v1/graphql". 37 | During startup all components that implement "GraphQlFields" interface will be applied on the GraphQL schema exposed by the controller. 38 | 39 | An example from the sample project: 40 | ```java 41 | package com.merapar.graphql.sample.fields; 42 | 43 | import com.merapar.graphql.GraphQlFields; 44 | import graphql.schema.GraphQLFieldDefinition; 45 | import org.springframework.stereotype.Component; 46 | 47 | import java.util.Collections; 48 | import java.util.List; 49 | 50 | import static graphql.Scalars.GraphQLString; 51 | import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; 52 | 53 | @Component 54 | public class HelloWorldFields implements GraphQlFields { 55 | 56 | @Override 57 | public List getQueryFields() { 58 | return Collections.singletonList( 59 | newFieldDefinition() 60 | .type(GraphQLString) 61 | .name("hello") 62 | .staticValue("world") 63 | .build() 64 | ); 65 | } 66 | 67 | @Override 68 | public List getMutationFields() { 69 | return Collections.emptyList(); 70 | } 71 | } 72 | ``` 73 | 74 | 75 | #### Configuration 76 | 77 | The following default properties can be configured via properties file: 78 | 79 | ```yaml 80 | com.merapar.graphql: 81 | rootQueryName: "queries" 82 | rootQueryDescription: "" 83 | rootMutationName: "mutations" 84 | rootMutationDescription: "" 85 | requestMapping: 86 | path: "/v1/graphql" 87 | executor: 88 | minimumThreadPoolSizeQuery: 10 89 | maximumThreadPoolSizeQuery: 20 90 | keepAliveTimeInSecondsQuery: 30 91 | minimumThreadPoolSizeMutation: 10 92 | maximumThreadPoolSizeMutation: 20 93 | keepAliveTimeInSecondsMutation: 30 94 | minimumThreadPoolSizeSubscription: 10 95 | maximumThreadPoolSizeSubscription: 20 96 | keepAliveTimeInSecondsSubscription: 30 97 | 98 | ``` 99 | 100 | 101 | #### How to use the latest release with Maven 102 | 103 | Dependency: 104 | 105 | ```xml 106 | 107 | com.merapar 108 | graphql-spring-boot-starter 109 | 1.0.2 110 | 111 | 112 | ``` 113 | 114 | 115 | #### How to use the latest build with Maven 116 | 117 | Add the repository: 118 | 119 | ```xml 120 | 121 | bintray-merapar-maven 122 | bintray 123 | http://dl.bintray.com/merapar/maven 124 | 125 | 126 | ``` 127 | 128 | Dependency: 129 | 130 | ```xml 131 | 132 | com.merapar 133 | graphql-spring-boot-starter 134 | 1.0.3-alpha 135 | 136 | 137 | ``` 138 | 139 | 140 | ### Versioning 141 | 142 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/merapar/graphql-spring-boot-starter/tags). 143 | 144 | 145 | ### Code of Conduct 146 | 147 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). 148 | By contributing to this project (commenting or opening PR/Issues etc) you are agreeing to follow this conduct, so please 149 | take the time to read it. 150 | 151 | 152 | ### Contributions 153 | 154 | Every contribution to make this project better is welcome: Thank you! 155 | 156 | In order to make this a pleasant as possible for everybody involved, here are some tips: 157 | 158 | - Respect the [Code of Conduct](#code-of-conduct) 159 | - Before opening an Issue to report a bug, please try the latest development version. It can happen that the problem is already solved. 160 | - Please use Markdown to format your comments properly. If you are not familiar with that: [Getting started with writing and formatting on GitHub](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/) 161 | - For Pull Requests: 162 | - Here are some [general tips](https://github.com/blog/1943-how-to-write-the-perfect-pull-request) 163 | - Please be a as focused and clear as possible and don't mix concerns. This includes refactorings mixed with bug-fixes/features, see [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) 164 | - It would be good to add a automatic test. All tests are written in JUnit and optional with [Spring Boot Test](http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html). 165 | 166 | 167 | ### Acknowledgment 168 | 169 | This implementation is based on the [graphql-java](https://github.com/graphql-java/graphql-java) project. 170 | 171 | 172 | ### License 173 | 174 | GraphQL Spring boot starter is licensed under the MIT License. See [LICENSE](LICENSE.md) for details. 175 | 176 | Copyright (c) 2016 Merapar Technologies 177 | 178 | [graphql-java License](https://github.com/graphql-java/graphql-java/blob/master/LICENSE.md) 179 | -------------------------------------------------------------------------------- /graphql-core/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.merapar 6 | graphql-spring-boot-starter 7 | 1.0.3-alpha 8 | jar 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 1.4.7.RELEASE 14 | 15 | 16 | graphql-spring-boot-starter 17 | This is a Spring boot starter project for the GraphQL Java project. 18 | https://github.com/merapar/graphql-spring-boot-starter 19 | 2016 20 | 21 | 22 | 23 | MIT 24 | https://github.com/merapar/graphql-spring-boot-starter/blob/master/LICENSE.md 25 | repo 26 | 27 | 28 | 29 | 30 | 31 | Jan Fockaert 32 | jan.fockaert@merapar.com 33 | Merapar Technologies 34 | https://www.merapartechnologies.com 35 | 36 | 37 | 38 | 39 | scm:git:git://github.com/merapar/graphql-spring-boot-starter.git 40 | scm:git:ssh://github.com:merapar/graphql-spring-boot-starter.git 41 | http://github.com/merapar/graphql-spring-boot-starter/tree/master 42 | 43 | 44 | 45 | UTF-8 46 | UTF-8 47 | 1.8 48 | 49 | 50 | 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-web 59 | 60 | 61 | org.springframework 62 | spring-context 63 | 64 | 65 | com.graphql-java 66 | graphql-java 67 | 6.0 68 | 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-test 74 | test 75 | 76 | 77 | junit 78 | junit 79 | test 80 | 81 | 82 | org.assertj 83 | assertj-core 84 | test 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.apache.maven.plugins 92 | maven-surefire-plugin 93 | 94 | 95 | org.jacoco 96 | jacoco-maven-plugin 97 | 0.7.7.201606060606 98 | 99 | 100 | 101 | prepare-agent 102 | 103 | 104 | 105 | report 106 | test 107 | 108 | report 109 | 110 | 111 | 112 | 113 | 114 | maven-source-plugin 115 | 3.0.1 116 | 117 | 118 | attach-sources 119 | verify 120 | 121 | jar-no-fork 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-javadoc-plugin 129 | 130 | 131 | attach-javadocs 132 | 133 | jar 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/GraphQlAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql; 2 | 3 | import com.merapar.graphql.controller.GraphQlControllerImpl; 4 | import com.merapar.graphql.executor.GraphQlExecutorProperties; 5 | import com.merapar.graphql.executor.GraphQlExecutorImpl; 6 | import com.merapar.graphql.schema.GraphQlSchemaBuilderImpl; 7 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Import; 10 | 11 | @Configuration 12 | @EnableConfigurationProperties({ 13 | GraphQlProperties.class, 14 | GraphQlExecutorProperties.class 15 | }) 16 | @Import({ 17 | GraphQlExecutorImpl.class, 18 | GraphQlSchemaBuilderImpl.class, 19 | GraphQlControllerImpl.class 20 | }) 21 | public class GraphQlAutoConfiguration { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/GraphQlFields.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql; 2 | 3 | import graphql.schema.GraphQLFieldDefinition; 4 | 5 | import java.util.List; 6 | 7 | public interface GraphQlFields { 8 | List getQueryFields(); 9 | List getMutationFields(); 10 | } 11 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/GraphQlProperties.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | @ConfigurationProperties(prefix = "com.merapar.graphql") 8 | public class GraphQlProperties { 9 | 10 | @Getter 11 | @Setter 12 | private String rootQueryName = "queries"; 13 | 14 | @Getter 15 | @Setter 16 | private String rootQueryDescription = ""; 17 | 18 | @Getter 19 | @Setter 20 | private String rootMutationName = "mutations"; 21 | 22 | @Getter 23 | @Setter 24 | private String rootMutationDescription = ""; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/Scalars.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql; 2 | 3 | import graphql.language.StringValue; 4 | import graphql.schema.Coercing; 5 | import graphql.schema.GraphQLScalarType; 6 | 7 | import java.time.Duration; 8 | import java.time.Instant; 9 | 10 | public class Scalars { 11 | public final static GraphQLScalarType GraphQlInstant = new GraphQLScalarType("Instant", "Built-in java.time.Instant", new Coercing() { 12 | 13 | @Override 14 | public Instant serialize(Object input) { 15 | return getInstant(input); 16 | } 17 | 18 | @Override 19 | public Instant parseValue(Object input) { 20 | return getInstant(input); 21 | } 22 | 23 | @Override 24 | public Instant parseLiteral(Object input) { 25 | return getInstant(input); 26 | } 27 | 28 | private Instant getInstant(Object input) { 29 | if (input instanceof Instant) { 30 | return (Instant) input; 31 | } else if (input instanceof String) { 32 | return Instant.parse((String) input); 33 | } else if (input instanceof StringValue) { 34 | return Instant.parse(((StringValue) input).getValue()); 35 | } 36 | 37 | return null; 38 | } 39 | }); 40 | 41 | public final static GraphQLScalarType GraphQlDuration = new GraphQLScalarType("Duration", "Built-in java.time.Duration", new Coercing() { 42 | @Override 43 | public Duration serialize(Object input) { 44 | return getDuration(input); 45 | } 46 | 47 | @Override 48 | public Duration parseValue(Object input) { 49 | return getDuration(input); 50 | } 51 | 52 | @Override 53 | public Duration parseLiteral(Object input) { 54 | return getDuration(input); 55 | } 56 | 57 | private Duration getDuration(Object input) { 58 | if (input instanceof Duration) { 59 | return (Duration) input; 60 | } else if (input instanceof String) { 61 | return Duration.parse((String) input); 62 | } else if (input instanceof StringValue) { 63 | return Duration.parse(((StringValue) input).getValue()); 64 | } 65 | return null; 66 | } 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/base/GraphQlFieldsHelper.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.base; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | 8 | public class GraphQlFieldsHelper { 9 | public static String INPUT = "input"; 10 | public static String FILTER = "filter"; 11 | 12 | public static TypedValueMap getInputMap(DataFetchingEnvironment environment) { 13 | return new TypedValueMap(environment.getArgument(INPUT)); 14 | } 15 | 16 | public static TypedValueMap getFilterMap(DataFetchingEnvironment environment) { 17 | Map filterMap = environment.getArgument(FILTER); 18 | 19 | if (filterMap == null) { 20 | return new TypedValueMap(); 21 | } 22 | 23 | return new TypedValueMap(filterMap); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/base/TypedValueMap.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.base; 2 | 3 | import lombok.EqualsAndHashCode; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | @EqualsAndHashCode 9 | public class TypedValueMap { 10 | 11 | private Map map; 12 | 13 | public TypedValueMap() { 14 | this(null); 15 | } 16 | 17 | public TypedValueMap(Map map) { 18 | this.map = map == null ? new HashMap<>() : map; 19 | } 20 | 21 | public boolean containsKey(String name) { 22 | return map.containsKey(name); 23 | } 24 | 25 | public T get(String name) { 26 | return (T) this.map.get(name); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/controller/GraphQlController.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.controller; 2 | 3 | public interface GraphQlController { 4 | } 5 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/controller/GraphQlControllerImpl.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.controller; 2 | 3 | import com.merapar.graphql.executor.GraphQlExecutor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import lombok.val; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.Map; 12 | import java.util.UUID; 13 | 14 | @ConditionalOnMissingBean(GraphQlController.class) 15 | @Controller 16 | @Slf4j 17 | public class GraphQlControllerImpl implements GraphQlController { 18 | 19 | @Autowired 20 | private GraphQlExecutor graphQlExecutor; 21 | 22 | @CrossOrigin 23 | @RequestMapping( 24 | path = "${com.merapar.graphql.requestMapping.path:v1/graphql}", 25 | method = RequestMethod.POST 26 | ) 27 | @ResponseBody 28 | public Object executeOperation(@RequestBody Map body) { 29 | val startTime = System.currentTimeMillis(); 30 | val uuid = UUID.randomUUID().toString(); 31 | 32 | log.debug("Start processing graphQL request {}", uuid); 33 | val requestResult = graphQlExecutor.executeRequest(body); 34 | 35 | log.debug("Finished processing graphQL request {} in {} ms", uuid, System.currentTimeMillis() - startTime); 36 | 37 | return requestResult; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/executor/GraphQlExecutor.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.executor; 2 | 3 | import java.util.Map; 4 | 5 | public interface GraphQlExecutor { 6 | Object executeRequest(Map requestBody); 7 | } 8 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/executor/GraphQlExecutorImpl.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.executor; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.merapar.graphql.schema.GraphQlSchemaBuilder; 6 | import graphql.GraphQL; 7 | import graphql.GraphQLException; 8 | import graphql.execution.ExecutionStrategy; 9 | import graphql.execution.ExecutorServiceExecutionStrategy; 10 | import lombok.extern.slf4j.Slf4j; 11 | import lombok.val; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 14 | import org.springframework.scheduling.concurrent.CustomizableThreadFactory; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.util.StringUtils; 17 | 18 | import javax.annotation.PostConstruct; 19 | import java.io.IOException; 20 | import java.util.Collections; 21 | import java.util.HashMap; 22 | import java.util.LinkedHashMap; 23 | import java.util.Map; 24 | import java.util.concurrent.SynchronousQueue; 25 | import java.util.concurrent.ThreadPoolExecutor; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | @ConditionalOnMissingBean(GraphQlExecutor.class) 29 | @Component 30 | @Slf4j 31 | public class GraphQlExecutorImpl implements GraphQlExecutor { 32 | 33 | @Autowired 34 | private GraphQlExecutorProperties processorProperties; 35 | 36 | @Autowired 37 | private ObjectMapper jacksonObjectMapper; 38 | 39 | @Autowired 40 | private GraphQlSchemaBuilder schemaBuilder; 41 | 42 | private TypeReference> typeRefReadJsonString = new TypeReference>() { 43 | }; 44 | 45 | private GraphQL graphQL; 46 | 47 | @PostConstruct 48 | private void postConstruct() { 49 | graphQL = createGraphQL(); 50 | } 51 | 52 | protected GraphQL createGraphQL() { 53 | return GraphQL.newGraphQL(schemaBuilder.getSchema()) 54 | .queryExecutionStrategy(createQueryExecutionStrategy()) 55 | .mutationExecutionStrategy(createMutationExecutionStrategy()) 56 | .subscriptionExecutionStrategy(createSubscriptionExecutionStrategy()) 57 | .build(); 58 | } 59 | 60 | protected ExecutionStrategy createQueryExecutionStrategy() { 61 | return createExecutionStrategy( 62 | processorProperties.getMinimumThreadPoolSizeQuery(), 63 | processorProperties.getMaximumThreadPoolSizeQuery(), 64 | processorProperties.getKeepAliveTimeInSecondsQuery(), 65 | "graphql-query-thread-" 66 | ); 67 | } 68 | 69 | protected ExecutionStrategy createMutationExecutionStrategy() { 70 | return createExecutionStrategy( 71 | processorProperties.getMinimumThreadPoolSizeMutation(), 72 | processorProperties.getMaximumThreadPoolSizeMutation(), 73 | processorProperties.getKeepAliveTimeInSecondsMutation(), 74 | "graphql-mutation-thread-" 75 | ); 76 | } 77 | 78 | protected ExecutionStrategy createSubscriptionExecutionStrategy() { 79 | return createExecutionStrategy( 80 | processorProperties.getMinimumThreadPoolSizeSubscription(), 81 | processorProperties.getMaximumThreadPoolSizeSubscription(), 82 | processorProperties.getKeepAliveTimeInSecondsSubscription(), 83 | "graphql-subscription-thread-" 84 | ); 85 | } 86 | 87 | private ExecutionStrategy createExecutionStrategy(Integer minimumThreadPoolSize, Integer maximumThreadPoolSize, Integer keepAliveTimeInSeconds, String threadNamePrefix) { 88 | return new ExecutorServiceExecutionStrategy(new ThreadPoolExecutor( 89 | minimumThreadPoolSize, 90 | maximumThreadPoolSize, 91 | keepAliveTimeInSeconds, 92 | TimeUnit.SECONDS, 93 | new SynchronousQueue<>(), 94 | new CustomizableThreadFactory(threadNamePrefix), 95 | new ThreadPoolExecutor.CallerRunsPolicy()) 96 | ); 97 | } 98 | 99 | protected void beforeExecuteRequest(String query, String operationName, Map context, Map variables) { 100 | } 101 | 102 | @Override 103 | public Object executeRequest(Map requestBody) { 104 | val query = (String) requestBody.get("query"); 105 | val operationName = (String) requestBody.get("operationName"); 106 | val variables = getVariablesFromRequest(requestBody); 107 | val context = new HashMap(); 108 | 109 | beforeExecuteRequest(query, operationName, context, variables); 110 | val executionResult = graphQL.execute(query, operationName, context, variables); 111 | 112 | val result = new LinkedHashMap(); 113 | 114 | if (executionResult.getErrors().size() > 0) { 115 | result.put("errors", executionResult.getErrors()); 116 | log.error("Errors: {}", executionResult.getErrors()); 117 | } 118 | result.put("data", executionResult.getData()); 119 | 120 | return result; 121 | } 122 | 123 | private Map getVariablesFromRequest(Map requestBody) { 124 | val variablesFromRequest = requestBody.get("variables"); 125 | 126 | if (variablesFromRequest == null) { 127 | return Collections.emptyMap(); 128 | } 129 | 130 | if (variablesFromRequest instanceof String) { 131 | if (StringUtils.hasText((String) variablesFromRequest)) { 132 | return getVariablesMapFromString((String) variablesFromRequest); 133 | } 134 | } else if (variablesFromRequest instanceof Map) { 135 | return (Map) variablesFromRequest; 136 | } else { 137 | throw new GraphQLException("Incorrect variables"); 138 | } 139 | 140 | return Collections.emptyMap(); 141 | } 142 | 143 | private Map getVariablesMapFromString(String variablesFromRequest) { 144 | try { 145 | return jacksonObjectMapper.readValue(variablesFromRequest, typeRefReadJsonString); 146 | } catch (IOException exception) { 147 | throw new GraphQLException("Cannot parse variables", exception); 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/executor/GraphQlExecutorProperties.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.executor; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | @ConfigurationProperties(prefix = "com.merapar.graphql.executor") 8 | public class GraphQlExecutorProperties { 9 | 10 | @Getter 11 | @Setter 12 | private Integer minimumThreadPoolSizeQuery = 10; 13 | 14 | @Getter 15 | @Setter 16 | private Integer maximumThreadPoolSizeQuery = 20; 17 | 18 | @Getter 19 | @Setter 20 | private Integer keepAliveTimeInSecondsQuery = 30; 21 | 22 | @Getter 23 | @Setter 24 | private Integer minimumThreadPoolSizeMutation = 10; 25 | 26 | @Getter 27 | @Setter 28 | private Integer maximumThreadPoolSizeMutation = 20; 29 | 30 | @Getter 31 | @Setter 32 | private Integer keepAliveTimeInSecondsMutation = 30; 33 | 34 | @Getter 35 | @Setter 36 | private Integer minimumThreadPoolSizeSubscription = 10; 37 | 38 | @Getter 39 | @Setter 40 | private Integer maximumThreadPoolSizeSubscription = 20; 41 | 42 | @Getter 43 | @Setter 44 | private Integer keepAliveTimeInSecondsSubscription = 30; 45 | } 46 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/schema/GraphQlSchemaBuilder.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.schema; 2 | 3 | import graphql.schema.GraphQLSchema; 4 | 5 | public interface GraphQlSchemaBuilder { 6 | GraphQLSchema getSchema(); 7 | } 8 | -------------------------------------------------------------------------------- /graphql-core/src/main/java/com/merapar/graphql/schema/GraphQlSchemaBuilderImpl.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.schema; 2 | 3 | import com.merapar.graphql.GraphQlProperties; 4 | import com.merapar.graphql.GraphQlFields; 5 | import graphql.schema.GraphQLObjectType; 6 | import graphql.schema.GraphQLSchema; 7 | import lombok.Getter; 8 | import lombok.extern.slf4j.Slf4j; 9 | import lombok.val; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.StringUtils; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.util.List; 17 | 18 | import static graphql.Scalars.GraphQLString; 19 | import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; 20 | import static graphql.schema.GraphQLObjectType.newObject; 21 | 22 | @ConditionalOnMissingBean(GraphQlSchemaBuilder.class) 23 | @Component 24 | @Slf4j 25 | public class GraphQlSchemaBuilderImpl implements GraphQlSchemaBuilder { 26 | 27 | private GraphQlProperties properties; 28 | private List graphQlFieldsDefinitions; 29 | 30 | @Getter 31 | private GraphQLSchema schema; 32 | 33 | @Autowired 34 | public GraphQlSchemaBuilderImpl(GraphQlProperties properties, List graphQlFieldsDefinitions) { 35 | this.properties = properties; 36 | this.graphQlFieldsDefinitions = graphQlFieldsDefinitions; 37 | } 38 | 39 | @PostConstruct 40 | public void postConstruct() { 41 | 42 | GraphQLObjectType.Builder queryBuilder = newObject().name(properties.getRootQueryName()); 43 | GraphQLObjectType.Builder mutationBuilder = newObject().name(properties.getRootMutationName()); 44 | 45 | if (StringUtils.hasText(properties.getRootQueryDescription())) { 46 | queryBuilder = queryBuilder.description(properties.getRootQueryDescription()); 47 | } 48 | 49 | if (StringUtils.hasText(properties.getRootMutationDescription())) { 50 | mutationBuilder = mutationBuilder.description(properties.getRootMutationDescription()); 51 | } 52 | 53 | buildSchemaFromDefinitions(queryBuilder, mutationBuilder); 54 | } 55 | 56 | private void buildSchemaFromDefinitions(GraphQLObjectType.Builder queryBuilder, GraphQLObjectType.Builder mutationBuilder) { 57 | boolean foundQueryDefinitions = false; 58 | boolean foundMutationDefinitions = false; 59 | 60 | for(val graphQlFieldsDefinition : graphQlFieldsDefinitions){ 61 | 62 | val queryFields = graphQlFieldsDefinition.getQueryFields(); 63 | if (queryFields != null && queryFields.size() > 0) { 64 | queryBuilder = queryBuilder.fields(queryFields); 65 | foundQueryDefinitions = true; 66 | } 67 | 68 | val mutationFields = graphQlFieldsDefinition.getMutationFields(); 69 | if (mutationFields != null && mutationFields.size() > 0) { 70 | mutationBuilder = mutationBuilder.fields(mutationFields); 71 | foundMutationDefinitions = true; 72 | } 73 | } 74 | 75 | buildSchema(queryBuilder, mutationBuilder, foundQueryDefinitions, foundMutationDefinitions); 76 | } 77 | 78 | private void buildSchema(GraphQLObjectType.Builder queryBuilder, GraphQLObjectType.Builder mutationBuilder, boolean foundQueryDefinitions, boolean foundMutationDefinitions) { 79 | log.debug("Start building graphql schema"); 80 | 81 | GraphQLSchema.Builder schemaBuilder = GraphQLSchema.newSchema(); 82 | 83 | if (foundQueryDefinitions) { 84 | schemaBuilder = schemaBuilder.query(queryBuilder.build()); 85 | } 86 | 87 | if (foundMutationDefinitions) { 88 | schemaBuilder = schemaBuilder.mutation(mutationBuilder.build()); 89 | } 90 | 91 | if (foundQueryDefinitions || foundMutationDefinitions) { 92 | schema = schemaBuilder.build(); 93 | } else { 94 | schema = generateGettingStartedGraphQlSchema(); 95 | } 96 | } 97 | 98 | private GraphQLSchema generateGettingStartedGraphQlSchema() { 99 | val gettingStartedType = newObject() 100 | .name("gettingStartedQuery") 101 | .field(newFieldDefinition() 102 | .type(GraphQLString) 103 | .name("gettingStarted") 104 | .staticValue("Create a component and implement GraphQlFields interface.")) 105 | .build(); 106 | 107 | return GraphQLSchema.newSchema() 108 | .query(gettingStartedType) 109 | .build(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /graphql-core/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.merapar.graphql.GraphQlAutoConfiguration -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/GraphQlPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @RunWith(SpringRunner.class) 13 | @ContextConfiguration(classes = GraphQlPropertiesTestConfiguration.class) 14 | @SpringBootTest(properties = { 15 | "com.merapar.graphql.rootQueryName=serviceQueries", 16 | "com.merapar.graphql.rootQueryDescription=The service for queries", 17 | "com.merapar.graphql.rootMutationName=serviceMutations", 18 | "com.merapar.graphql.rootMutationDescription=The service for mutations" 19 | }) 20 | public class GraphQlPropertiesTest { 21 | 22 | @Autowired 23 | private GraphQlProperties graphQlProperties; 24 | 25 | @Test 26 | public void testPropertyInjection() { 27 | assertThat(graphQlProperties.getRootQueryName()).isEqualTo("serviceQueries"); 28 | assertThat(graphQlProperties.getRootQueryDescription()).isEqualTo("The service for queries"); 29 | assertThat(graphQlProperties.getRootMutationName()).isEqualTo("serviceMutations"); 30 | assertThat(graphQlProperties.getRootMutationDescription()).isEqualTo("The service for mutations"); 31 | } 32 | } -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/GraphQlPropertiesTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql; 2 | 3 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @EnableConfigurationProperties(GraphQlProperties.class) 8 | public class GraphQlPropertiesTestConfiguration { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/ScalarsTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql; 2 | 3 | import graphql.language.StringValue; 4 | import lombok.val; 5 | import org.junit.Test; 6 | 7 | import java.time.Duration; 8 | import java.time.LocalDateTime; 9 | import java.time.ZoneOffset; 10 | import java.time.format.DateTimeParseException; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 14 | 15 | public class ScalarsTest { 16 | 17 | @Test 18 | public void instant_ParseValue_String() { 19 | // Given 20 | val input = "2016-11-21T06:00:00Z"; 21 | 22 | // When 23 | val parseResult = Scalars.GraphQlInstant.getCoercing().parseValue(input); 24 | 25 | // Then 26 | assertThat(parseResult).isEqualTo(LocalDateTime.of(2016, 11, 21, 6, 0).toInstant(ZoneOffset.UTC)); 27 | } 28 | 29 | @Test 30 | public void instant_ParseValue_Instant() { 31 | // Given 32 | val input = LocalDateTime.of(2016, 11, 21, 6, 0).toInstant(ZoneOffset.UTC); 33 | 34 | // When 35 | val parseResult = Scalars.GraphQlInstant.getCoercing().parseValue(input); 36 | 37 | // Then 38 | assertThat(parseResult).isEqualTo(LocalDateTime.of(2016, 11, 21, 6, 0).toInstant(ZoneOffset.UTC)); 39 | } 40 | 41 | @Test 42 | public void instant_ParseLiteral_OtherType_ShouldReturnNull() { 43 | // Given 44 | Integer input = 4; 45 | 46 | // When 47 | val parseResult = Scalars.GraphQlInstant.getCoercing().parseLiteral(input); 48 | 49 | // Then 50 | assertThat(parseResult).isNull(); 51 | } 52 | 53 | @Test 54 | public void instant_Serialize_StringValue() { 55 | // Given 56 | val input = new StringValue("2016-11-21T06:00:00Z"); 57 | 58 | // When 59 | val parseResult = Scalars.GraphQlInstant.getCoercing().serialize(input); 60 | 61 | // Then 62 | assertThat(parseResult).isEqualTo(LocalDateTime.of(2016, 11, 21, 6, 0).toInstant(ZoneOffset.UTC)); 63 | } 64 | 65 | @Test 66 | public void instant_ParseLiteral_WrongStringInput_ShouldThrowDateTimeParseException() { 67 | // Given 68 | val input = new StringValue("2016-11-21T06:00:"); 69 | 70 | // When 71 | val parseExcpetion = assertThatThrownBy(() -> Scalars.GraphQlInstant.getCoercing().parseLiteral(input)); 72 | 73 | // Then 74 | parseExcpetion.isExactlyInstanceOf(DateTimeParseException.class).hasMessage("Text '2016-11-21T06:00:' could not be parsed at index 17"); 75 | } 76 | 77 | @Test 78 | public void duration_ParseValue_String() { 79 | // Given 80 | val input = "PT48H"; 81 | 82 | // When 83 | val parseResult = Scalars.GraphQlDuration.getCoercing().parseValue(input); 84 | 85 | // Then 86 | assertThat(parseResult).isEqualTo(Duration.ofHours(48)); 87 | } 88 | 89 | @Test 90 | public void duration_ParseValue_Duration() { 91 | // Given 92 | val input = Duration.ofHours(48); 93 | 94 | // When 95 | val parseResult = Scalars.GraphQlDuration.getCoercing().parseValue(input); 96 | 97 | // Then 98 | assertThat(parseResult).isEqualTo(Duration.ofHours(48)); 99 | } 100 | 101 | @Test 102 | public void duration_ParseLiteral_OtherType_ShouldReturnNull() { 103 | // Given 104 | Integer input = 4; 105 | 106 | // When 107 | val parseResult = Scalars.GraphQlDuration.getCoercing().parseLiteral(input); 108 | 109 | // Then 110 | assertThat(parseResult).isNull(); 111 | } 112 | 113 | @Test 114 | public void duration_Serialize_StringValue() { 115 | // Given 116 | val input = new StringValue("PT48H"); 117 | 118 | // When 119 | val parseResult = Scalars.GraphQlDuration.getCoercing().serialize(input); 120 | 121 | // Then 122 | assertThat(parseResult).isEqualTo(Duration.ofHours(48)); 123 | } 124 | 125 | @Test 126 | public void duration_ParseLiteral_WrongStringInput_ShouldThrowDateTimeException() { 127 | // Given 128 | val input = new StringValue("P48H"); 129 | 130 | // When 131 | val parseExcpetion = assertThatThrownBy(() -> Scalars.GraphQlDuration.getCoercing().parseLiteral(input)); 132 | 133 | // Then 134 | parseExcpetion.isExactlyInstanceOf(DateTimeParseException.class).hasMessage("Text cannot be parsed to a Duration"); 135 | } 136 | } -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/base/TypedValueMapTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.base; 2 | 3 | import lombok.val; 4 | import org.assertj.core.api.Assertions; 5 | import org.junit.Test; 6 | 7 | import java.util.Collections; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class TypedValueMapTest { 12 | 13 | @Test 14 | public void equalsImplemented() { 15 | // Given 16 | val inputMap = new TypedValueMap(Collections.singletonMap("key", "value")); 17 | val inputMap2 = new TypedValueMap(Collections.singletonMap("key", "value")); 18 | 19 | // Wen 20 | val result = inputMap.equals(inputMap2); 21 | 22 | // Then 23 | Assertions.assertThat(result).isTrue(); 24 | } 25 | } -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/controller/GraphQlControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.controller; 2 | 3 | import com.merapar.graphql.controller.data.User; 4 | import com.merapar.graphql.controller.data.UserDataFetcher; 5 | import lombok.val; 6 | import org.assertj.core.api.Assertions; 7 | import org.json.JSONObject; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.test.context.ContextConfiguration; 15 | import org.springframework.test.context.TestPropertySource; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | import org.springframework.test.web.servlet.ResultActions; 19 | 20 | import java.util.Collections; 21 | import java.util.LinkedHashMap; 22 | import java.util.Map; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 28 | 29 | @RunWith(SpringRunner.class) 30 | @ContextConfiguration(classes = GraphQlControllerTestConfiguration.class) 31 | @WebMvcTest(GraphQlControllerImpl.class) 32 | @TestPropertySource(properties = {"com.merapar.graphql.requestMapping.path=" + GraphQlControllerTest.GRAPH_QL_PATH}) 33 | public class GraphQlControllerTest { 34 | 35 | @Autowired 36 | private MockMvc mockMvc; 37 | 38 | @Autowired 39 | private UserDataFetcher userDataFetcher; 40 | 41 | final static String GRAPH_QL_PATH = "v3/graphql"; 42 | 43 | @Before 44 | public void setup() { 45 | userDataFetcher.users = new LinkedHashMap<>(); 46 | userDataFetcher.users.put(1, new User(1, "Jan")); 47 | userDataFetcher.users.put(2, new User(2, "Peter")); 48 | } 49 | 50 | @Test 51 | public void getUsers() throws Exception { 52 | // Given 53 | val query = "{" + 54 | "users {" + 55 | " id" + 56 | " name" + 57 | "}}"; 58 | 59 | // When 60 | val postResult = performGraphQlPost(query); 61 | 62 | // Then 63 | postResult.andExpect(status().isOk()) 64 | .andExpect(jsonPath("$.errors").doesNotExist()) 65 | .andExpect(jsonPath("$.data.users[0].id").value("1")) 66 | .andExpect(jsonPath("$.data.users[0].name").value("Jan")) 67 | .andExpect(jsonPath("$.data.users[1].id").value("2")) 68 | .andExpect(jsonPath("$.data.users[1].name").value("Peter")); 69 | } 70 | 71 | @Test 72 | public void addUser() throws Exception { 73 | // Given 74 | val query = "mutation addUser($input: addUserInput!) {" + 75 | " addUser(input: $input) {" + 76 | " id" + 77 | " name" + 78 | " }\n" + 79 | "}"; 80 | 81 | val variables = new LinkedHashMap<>(); 82 | variables.put("id", 1234); 83 | variables.put("name", "added user"); 84 | 85 | // When 86 | val postResult = performGraphQlPost(query, variables); 87 | 88 | // Then 89 | postResult.andExpect(status().isOk()) 90 | .andExpect(jsonPath("$.errors").doesNotExist()) 91 | .andExpect(jsonPath("$.data.addUser.id").value(1234)) 92 | .andExpect(jsonPath("$.data.addUser.name").value("added user")); 93 | 94 | Assertions.assertThat(userDataFetcher.users).containsKeys(1234); 95 | } 96 | 97 | @Test 98 | public void updateUser() throws Exception { 99 | // Given 100 | val query = "mutation updateUser($input: updateUserInput!) {" + 101 | " updateUser(input: $input) {" + 102 | " id" + 103 | " name" + 104 | " }\n" + 105 | "}"; 106 | 107 | val variables = new LinkedHashMap<>(); 108 | variables.put("id", 1); 109 | variables.put("name", "updated user"); 110 | 111 | // When 112 | val postResult = performGraphQlPost(query, variables); 113 | 114 | // Then 115 | postResult.andExpect(status().isOk()) 116 | .andExpect(jsonPath("$.errors").doesNotExist()) 117 | .andExpect(jsonPath("$.data.updateUser.id").value(1)) 118 | .andExpect(jsonPath("$.data.updateUser.name").value("updated user")); 119 | 120 | assertThat(userDataFetcher.users.get(1).getName()).isEqualTo("updated user"); 121 | } 122 | 123 | @Test 124 | public void deleteUser() throws Exception { 125 | // Given 126 | val query = "mutation deleteUser($input: deleteUserInput!) {" + 127 | " deleteUser(input: $input) {" + 128 | " id" + 129 | " name" + 130 | " }\n" + 131 | "}"; 132 | 133 | val variables = new LinkedHashMap<>(); 134 | variables.put("id", 2); 135 | 136 | // When 137 | val postResult = performGraphQlPost(query, variables); 138 | 139 | // Then 140 | postResult.andExpect(status().isOk()) 141 | .andExpect(jsonPath("$.errors").doesNotExist()) 142 | .andExpect(jsonPath("$.data.deleteUser.id").value(2)) 143 | .andExpect(jsonPath("$.data.deleteUser.name").value("Peter")); 144 | 145 | Assertions.assertThat(userDataFetcher.users).containsKeys(1); 146 | } 147 | 148 | @Test 149 | public void invalidName_shouldGiveBackErrors() throws Exception { 150 | // Given 151 | val query = "{" + 152 | "users {" + 153 | " invalidName" + 154 | " name" + 155 | "}}"; 156 | 157 | // When 158 | val postResult = performGraphQlPost(query); 159 | 160 | // Then 161 | postResult.andExpect(status().isOk()) 162 | .andExpect(jsonPath("$.errors").exists()); 163 | } 164 | 165 | private ResultActions performGraphQlPost(String query) throws Exception { 166 | return performGraphQlPost(query, null); 167 | } 168 | 169 | private ResultActions performGraphQlPost(String query, Map variables) throws Exception { 170 | return mockMvc.perform(post("/" + GRAPH_QL_PATH) 171 | .contentType(MediaType.APPLICATION_JSON) 172 | .content(generateRequest(query, variables)) 173 | ); 174 | } 175 | 176 | private String generateRequest(String query, Map variables) { 177 | val jsonObject = new JSONObject(); 178 | 179 | jsonObject.put("query", query); 180 | 181 | if (variables != null) { 182 | jsonObject.put("variables", Collections.singletonMap("input", variables)); 183 | } 184 | 185 | return jsonObject.toString(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/controller/GraphQlControllerTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.controller; 2 | 3 | import com.merapar.graphql.GraphQlAutoConfiguration; 4 | import com.merapar.graphql.controller.data.UserDataFetcher; 5 | import com.merapar.graphql.controller.data.UserFields; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import(GraphQlAutoConfiguration.class) 12 | public class GraphQlControllerTestConfiguration { 13 | 14 | @Bean 15 | public UserDataFetcher userDataFetcher() { 16 | return new UserDataFetcher(); 17 | } 18 | 19 | @Bean 20 | public UserFields userFields() { 21 | return new UserFields(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/controller/data/User.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.controller.data; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class User { 11 | private Integer id; 12 | private String name; 13 | } -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/controller/data/UserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.controller.data; 2 | 3 | import com.merapar.graphql.base.TypedValueMap; 4 | import lombok.val; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.*; 8 | 9 | @Component 10 | public class UserDataFetcher { 11 | 12 | public Map users = new HashMap<>(); 13 | 14 | public List getUsersByFilter(TypedValueMap arguments) { 15 | Integer id = arguments.get("id"); 16 | 17 | if (id != null) { 18 | return Collections.singletonList(users.get(id)); 19 | } else { 20 | return new ArrayList<>(users.values()); 21 | } 22 | } 23 | 24 | public User addUser(TypedValueMap arguments) { 25 | val user = new User(); 26 | 27 | user.setId(arguments.get("id")); 28 | user.setName(arguments.get("name")); 29 | 30 | users.put(user.getId(), user); 31 | 32 | return user; 33 | } 34 | 35 | public User updateUser(TypedValueMap arguments) { 36 | val user = users.get(arguments.get("id")); 37 | 38 | if (arguments.containsKey("name")) { 39 | user.setName(arguments.get("name")); 40 | } 41 | 42 | return user; 43 | } 44 | 45 | public User deleteUser(TypedValueMap arguments) { 46 | val user = users.get(arguments.get("id")); 47 | 48 | users.remove(user.getId()); 49 | 50 | return user; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/controller/data/UserFields.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.controller.data; 2 | 3 | import com.merapar.graphql.GraphQlFields; 4 | import graphql.Scalars; 5 | import graphql.schema.*; 6 | import lombok.Getter; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | import static com.merapar.graphql.base.GraphQlFieldsHelper.*; 15 | import static graphql.Scalars.GraphQLInt; 16 | import static graphql.Scalars.GraphQLString; 17 | import static graphql.schema.GraphQLArgument.newArgument; 18 | import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; 19 | import static graphql.schema.GraphQLInputObjectField.newInputObjectField; 20 | import static graphql.schema.GraphQLInputObjectType.newInputObject; 21 | import static graphql.schema.GraphQLObjectType.newObject; 22 | 23 | public class UserFields implements GraphQlFields { 24 | 25 | @Autowired 26 | private UserDataFetcher userDataFetcher; 27 | 28 | @Getter 29 | private GraphQLObjectType userType; 30 | 31 | private GraphQLInputObjectType addUserInputType; 32 | private GraphQLInputObjectType updateUserInputType; 33 | private GraphQLInputObjectType deleteUserInputType; 34 | 35 | @Getter 36 | private GraphQLInputObjectType filterUserInputType; 37 | 38 | @Getter 39 | private GraphQLFieldDefinition usersField; 40 | 41 | @Getter 42 | private GraphQLFieldDefinition addUserField; 43 | 44 | @Getter 45 | private GraphQLFieldDefinition updateUserField; 46 | 47 | @Getter 48 | private GraphQLFieldDefinition deleteUserField; 49 | 50 | @Getter 51 | private List queryFields; 52 | 53 | @Getter 54 | private List mutationFields; 55 | 56 | @PostConstruct 57 | public void postConstruct() { 58 | createTypes(); 59 | createFields(); 60 | queryFields = Collections.singletonList(usersField); 61 | mutationFields = Arrays.asList(addUserField, updateUserField, deleteUserField); 62 | } 63 | 64 | private void createTypes() { 65 | userType = newObject().name("user").description("A user") 66 | .field(newFieldDefinition().name("id").description("The id").type(GraphQLInt).build()) 67 | .field(newFieldDefinition().name("name").description("The network name").type(GraphQLString).build()) 68 | .build(); 69 | 70 | addUserInputType = newInputObject().name("addUserInput") 71 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 72 | .field(newInputObjectField().name("name").type(new GraphQLNonNull(Scalars.GraphQLString)).build()) 73 | .build(); 74 | 75 | updateUserInputType = newInputObject().name("updateUserInput") 76 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 77 | .field(newInputObjectField().name("name").type(GraphQLString).build()) 78 | .build(); 79 | 80 | deleteUserInputType = newInputObject().name("deleteUserInput") 81 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 82 | .build(); 83 | 84 | filterUserInputType = newInputObject().name("filterUserInput") 85 | .field(newInputObjectField().name("id").type(GraphQLString).build()) 86 | .build(); 87 | } 88 | 89 | private void createFields() { 90 | usersField = newFieldDefinition() 91 | .name("users").description("Provide an overview of all users") 92 | .type(new GraphQLList(userType)) 93 | .argument(newArgument().name(FILTER).type(filterUserInputType).build()) 94 | .dataFetcher(environment -> userDataFetcher.getUsersByFilter(getFilterMap(environment))) 95 | .build(); 96 | 97 | addUserField = newFieldDefinition() 98 | .name("addUser").description("Add new user") 99 | .type(userType) 100 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(addUserInputType)).build()) 101 | .dataFetcher(environment -> userDataFetcher.addUser(getInputMap(environment))) 102 | .build(); 103 | 104 | updateUserField = newFieldDefinition() 105 | .name("updateUser").description("Update existing user") 106 | .type(userType) 107 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(updateUserInputType)).build()) 108 | .dataFetcher(environment -> userDataFetcher.updateUser(getInputMap(environment))) 109 | .build(); 110 | 111 | deleteUserField = newFieldDefinition() 112 | .name("deleteUser").description("Delete existing user") 113 | .type(userType) 114 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(deleteUserInputType)).build()) 115 | .dataFetcher(environment -> userDataFetcher.deleteUser(getInputMap(environment))) 116 | .build(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/executor/GraphQlExecutorImplTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.executor; 2 | 3 | import graphql.GraphQLException; 4 | import lombok.val; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import java.util.LinkedHashMap; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 15 | 16 | @RunWith(SpringRunner.class) 17 | @ContextConfiguration(classes = GraphQlExecutorImplTestConfiguration.class) 18 | public class GraphQlExecutorImplTest { 19 | 20 | @Autowired 21 | private GraphQlExecutor graphQlExecutor; 22 | 23 | @Test 24 | public void processRequest_invalidVariablesJson_shouldThrowException() { 25 | // Given 26 | val request = new LinkedHashMap(); 27 | request.put("query", "dummy"); 28 | request.put("operationName", "dummy"); 29 | request.put("variables", "no JSON object"); 30 | 31 | // When 32 | val exception = assertThatThrownBy(() -> graphQlExecutor.executeRequest(request)); 33 | 34 | // Then 35 | exception.isExactlyInstanceOf(GraphQLException.class).hasMessage("Cannot parse variables"); 36 | } 37 | 38 | @Test 39 | public void processRequest_invalidVariablesType_shouldThrowException() { 40 | // Given 41 | val request = new LinkedHashMap(); 42 | request.put("query", "dummy"); 43 | request.put("operationName", "dummy"); 44 | request.put("variables", 1234); 45 | 46 | // When 47 | val exception = assertThatThrownBy(() -> graphQlExecutor.executeRequest(request)); 48 | 49 | // Then 50 | exception.isExactlyInstanceOf(GraphQLException.class).hasMessage("Incorrect variables"); 51 | } 52 | 53 | @Test 54 | public void processRequestWithoutVariables_shouldExecuteProcess() { 55 | // Given 56 | val request = new LinkedHashMap(); 57 | request.put("query", "dummy"); 58 | request.put("operationName", "dummy"); 59 | 60 | // When 61 | val result = graphQlExecutor.executeRequest(request); 62 | 63 | // Then 64 | assertThat(result).isNotNull(); 65 | } 66 | 67 | @Test 68 | public void processRequestWithEmptyString_shouldExecuteProcess() { 69 | // Given 70 | val request = new LinkedHashMap(); 71 | request.put("query", "dummy"); 72 | request.put("operationName", "dummy"); 73 | request.put("variables", ""); 74 | 75 | // When 76 | val result = graphQlExecutor.executeRequest(request); 77 | 78 | // Then 79 | assertThat(result).isNotNull(); 80 | } 81 | } -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/executor/GraphQlExecutorImplTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.executor; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.merapar.graphql.GraphQlProperties; 5 | import com.merapar.graphql.schema.GraphQlSchemaBuilder; 6 | import graphql.schema.GraphQLSchema; 7 | import lombok.val; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | import static org.mockito.Mockito.mock; 13 | import static org.mockito.Mockito.when; 14 | 15 | @Configuration 16 | public class GraphQlExecutorImplTestConfiguration { 17 | 18 | @Bean 19 | public GraphQlProperties configuration() { 20 | return new GraphQlProperties(); 21 | } 22 | 23 | @Bean 24 | public GraphQlSchemaBuilder schema() { 25 | val graphQlSchemaBuilder = mock(GraphQlSchemaBuilder.class); 26 | when(graphQlSchemaBuilder.getSchema()).thenReturn(mock(GraphQLSchema.class)); 27 | 28 | return graphQlSchemaBuilder; 29 | } 30 | 31 | @Bean 32 | public ObjectMapper objectMapper() { 33 | return new ObjectMapper(); 34 | } 35 | 36 | @Bean 37 | public GraphQlExecutorProperties graphQlProcessorProperties() { return new GraphQlExecutorProperties();} 38 | 39 | @Bean 40 | public GraphQlExecutor graphQlProcessor() { return new GraphQlExecutorImpl();} 41 | } 42 | -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/executor/GraphQlExecutorPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.executor; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @RunWith(SpringRunner.class) 13 | @ContextConfiguration(classes = GraphQlExecutorPropertiesTestConfiguration.class) 14 | @SpringBootTest(properties = { 15 | "com.merapar.graphql.executor.minimumThreadPoolSizeQuery=40", 16 | "com.merapar.graphql.executor.maximumThreadPoolSizeQuery=50", 17 | "com.merapar.graphql.executor.keepAliveTimeInSecondsQuery=60", 18 | "com.merapar.graphql.executor.minimumThreadPoolSizeMutation=41", 19 | "com.merapar.graphql.executor.maximumThreadPoolSizeMutation=51", 20 | "com.merapar.graphql.executor.keepAliveTimeInSecondsMutation=61", 21 | "com.merapar.graphql.executor.minimumThreadPoolSizeSubscription=42", 22 | "com.merapar.graphql.executor.maximumThreadPoolSizeSubscription=52", 23 | "com.merapar.graphql.executor.keepAliveTimeInSecondsSubscription=62", 24 | }) 25 | public class GraphQlExecutorPropertiesTest { 26 | 27 | @Autowired 28 | private GraphQlExecutorProperties graphQlExecutorProperties; 29 | 30 | @Test 31 | public void testPropertyInjection() { 32 | assertThat(graphQlExecutorProperties.getMinimumThreadPoolSizeQuery()).isEqualTo(40); 33 | assertThat(graphQlExecutorProperties.getMaximumThreadPoolSizeQuery()).isEqualTo(50); 34 | assertThat(graphQlExecutorProperties.getKeepAliveTimeInSecondsQuery()).isEqualTo(60); 35 | 36 | assertThat(graphQlExecutorProperties.getMinimumThreadPoolSizeMutation()).isEqualTo(41); 37 | assertThat(graphQlExecutorProperties.getMaximumThreadPoolSizeMutation()).isEqualTo(51); 38 | assertThat(graphQlExecutorProperties.getKeepAliveTimeInSecondsMutation()).isEqualTo(61); 39 | 40 | assertThat(graphQlExecutorProperties.getMinimumThreadPoolSizeSubscription()).isEqualTo(42); 41 | assertThat(graphQlExecutorProperties.getMaximumThreadPoolSizeSubscription()).isEqualTo(52); 42 | assertThat(graphQlExecutorProperties.getKeepAliveTimeInSecondsSubscription()).isEqualTo(62); 43 | } 44 | } -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/executor/GraphQlExecutorPropertiesTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.executor; 2 | 3 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @EnableConfigurationProperties(GraphQlExecutorProperties.class) 8 | public class GraphQlExecutorPropertiesTestConfiguration { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /graphql-core/src/test/java/com/merapar/graphql/schema/GraphQlSchemaBuilderImplTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.schema; 2 | 3 | import com.merapar.graphql.GraphQlProperties; 4 | import com.merapar.graphql.GraphQlFields; 5 | import graphql.AssertException; 6 | import graphql.schema.GraphQLFieldDefinition; 7 | import lombok.val; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | import static graphql.Scalars.GraphQLString; 17 | import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 20 | import static org.mockito.Mockito.mock; 21 | import static org.mockito.Mockito.when; 22 | 23 | public class GraphQlSchemaBuilderImplTest { 24 | 25 | private GraphQlProperties graphQlProperties; 26 | private GraphQlSchemaBuilderImpl graphQlSchemaBuilder; 27 | 28 | @Before 29 | public void setup() { 30 | graphQlProperties = mock(GraphQlProperties.class); 31 | 32 | when(graphQlProperties.getRootQueryName()).thenReturn("queries"); 33 | when(graphQlProperties.getRootMutationName()).thenReturn("mutations"); 34 | } 35 | 36 | @Test 37 | public void noMutations_shouldGenerateSchemaWithOnlyQueries() { 38 | // Given 39 | graphQlSchemaBuilder = new GraphQlSchemaBuilderImpl(graphQlProperties, getGraphQlFields(new DummyGraphQlFieldWithBase(true, false))); 40 | 41 | // When 42 | graphQlSchemaBuilder.postConstruct(); 43 | 44 | // Then 45 | val schema = graphQlSchemaBuilder.getSchema(); 46 | 47 | assertThat(schema).isNotNull(); 48 | assertThat(schema.getQueryType()).isNotNull(); 49 | assertThat(schema.getMutationType()).isNull(); 50 | } 51 | 52 | @Test 53 | public void noQueries_shouldThrowException() { 54 | // Given 55 | graphQlSchemaBuilder = new GraphQlSchemaBuilderImpl(graphQlProperties, getGraphQlFields(new DummyGraphQlFieldWithBase(false, true))); 56 | 57 | // When 58 | val exception = assertThatThrownBy(() -> graphQlSchemaBuilder.postConstruct()); 59 | 60 | // Then 61 | exception.isExactlyInstanceOf(AssertException.class).hasMessage("queryType can't be null"); 62 | } 63 | 64 | @Test 65 | public void configuredRootQueryAndMutationDescriptions_shouldBeAppliedOnSchema() { 66 | // Given 67 | graphQlSchemaBuilder = new GraphQlSchemaBuilderImpl(graphQlProperties, getGraphQlFields(new DummyGraphQlFieldWithBase(true, true))); 68 | when(graphQlProperties.getRootQueryDescription()).thenReturn("rootQueryDescription"); 69 | when(graphQlProperties.getRootMutationDescription()).thenReturn("rootMutationDescription"); 70 | 71 | // When 72 | graphQlSchemaBuilder.postConstruct(); 73 | 74 | // Then 75 | val schema = graphQlSchemaBuilder.getSchema(); 76 | 77 | assertThat(schema).isNotNull(); 78 | assertThat(schema.getQueryType().getDescription()).isEqualTo("rootQueryDescription"); 79 | assertThat(schema.getMutationType().getDescription()).isEqualTo("rootMutationDescription"); 80 | } 81 | 82 | @Test 83 | public void noQueriesAndMutations_shouldGenerateGettingStartedGraphQlSchema() { 84 | // Given 85 | graphQlSchemaBuilder = new GraphQlSchemaBuilderImpl(graphQlProperties, new ArrayList<>()); 86 | 87 | // When 88 | graphQlSchemaBuilder.postConstruct(); 89 | 90 | // Then 91 | val schema = graphQlSchemaBuilder.getSchema(); 92 | 93 | assertThat(schema).isNotNull(); 94 | assertThat(schema.getQueryType().getName()).isEqualTo("gettingStartedQuery"); 95 | } 96 | 97 | private List getGraphQlFields(GraphQlFields... fields) 98 | { 99 | return Arrays.asList(fields); 100 | } 101 | 102 | class DummyGraphQlFieldWithBase extends DummyGraphQlFieldsWithoutBase implements GraphQlFields { 103 | 104 | public DummyGraphQlFieldWithBase(boolean includeQueryField, boolean includeMutationField) { 105 | super(includeQueryField, includeMutationField); 106 | } 107 | } 108 | 109 | class DummyGraphQlFieldsWithoutBase { 110 | 111 | boolean includeQueryField; 112 | boolean includeMutationField; 113 | 114 | public DummyGraphQlFieldsWithoutBase(boolean includeQueryField, boolean includeMutationField) { 115 | this.includeQueryField = includeQueryField; 116 | this.includeMutationField = includeMutationField; 117 | } 118 | 119 | public List getQueryFields() { 120 | if (includeQueryField) { 121 | return Collections.singletonList( 122 | newFieldDefinition() 123 | .type(GraphQLString) 124 | .name("dummy") 125 | .staticValue("value") 126 | .build() 127 | ); 128 | } else { 129 | return Collections.emptyList(); 130 | } 131 | } 132 | 133 | public List getMutationFields() { 134 | if (includeMutationField) { 135 | return Collections.singletonList( 136 | newFieldDefinition() 137 | .type(GraphQLString) 138 | .name("dummy") 139 | .staticValue("value") 140 | .build() 141 | ); 142 | } else { 143 | return Collections.emptyList(); 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /graphql-sample/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Spring boot starter sample 2 | 3 | This sample demonstrates some of the features of the GraphQL spring boot starter project. 4 | 5 | The purpose of this readme is to get you started quickly. 6 | 7 | The endpoint can be found at `http://localhost:8080/v1/graphql` 8 | 9 | ## Graphql clients 10 | One example of a rest client with graphql support is [insomnia](https://insomnia.rest), [graphiql](https://github.com/graphql/graphiql) and [curl](https://curl.haxx.se/) can also be used. 11 | 12 | ## Examples 13 | 14 | Each example starts with a graphql request, a curl command and shows the expected outcome. 15 | 16 | ### Example Hello world 17 | 18 | Request: 19 | 20 | ```graphql 21 | { 22 | hello 23 | } 24 | ``` 25 | 26 | ```bash 27 | curl --request POST \ 28 | --url http://localhost:8080/v1/graphql \ 29 | --header 'content-type: application/json' \ 30 | --data '{"query":"{hello}","variables":"{}"}' 31 | ``` 32 | 33 | Expected response: 34 | 35 | ```json 36 | { 37 | "data": { 38 | "hello": "world" 39 | } 40 | } 41 | ``` 42 | 43 | ### Example query roles 44 | 45 | Request: 46 | 47 | ```graphql 48 | query { 49 | roles { 50 | id 51 | name 52 | } 53 | } 54 | ``` 55 | 56 | ```bash 57 | curl --request POST \ 58 | --url http://localhost:8080/v1/graphql \ 59 | --header 'content-type: application/json' \ 60 | --data '{"query":"query {roles {id name}}","variables":"{}"}' 61 | ``` 62 | 63 | Expected response: 64 | 65 | ```json 66 | { 67 | "data": { 68 | "roles": [] 69 | } 70 | } 71 | ``` 72 | 73 | ### Example add a role 74 | 75 | Request: 76 | 77 | ```graphql 78 | mutation addRoleMutation { 79 | addRole(input: { 80 | id: 1 81 | name: "root" 82 | }) { 83 | id 84 | } 85 | } 86 | ``` 87 | 88 | ```bash 89 | curl --request POST \ 90 | --url http://localhost:8080/v1/graphql \ 91 | --header 'content-type: application/json' \ 92 | --data '{"query":"mutation addRoleMutation {addRole(input: {id: 1 name: \"root\"}) {id}}","variables":"{}"}' 93 | ``` 94 | 95 | Expected response: 96 | 97 | ```json 98 | { 99 | "data": { 100 | "addRole": { 101 | "id": 1 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | ### Example add a user 108 | 109 | Request: 110 | 111 | ```graphql 112 | mutation addUserMutation { 113 | addUser(input: { 114 | id: 1234 115 | name: "New user" 116 | }) { 117 | roles { 118 | name 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | ```bash 125 | curl --request POST \ 126 | --url http://localhost:8080/v1/graphql \ 127 | --header 'content-type: application/json' \ 128 | --data '{"query":"mutation addUserMutation {addUser(input: {id: 1234 name: \"New user\"}) {roles{name}}}","variables":"{}"}' 129 | ``` 130 | 131 | Expected response: 132 | 133 | ```json 134 | { 135 | "data": { 136 | "addUser": { 137 | "roles": [ 138 | { 139 | "name": "Admin" 140 | } 141 | ] 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | :heavy_exclamation_mark: The response will be Admin in the roles because the sample is set-up in that way. When getting a user the role will always be Admin. Please consult the source code for all implementation details of this sample. 148 | -------------------------------------------------------------------------------- /graphql-sample/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.merapar 6 | graphql-spring-boot-starter-sample 7 | 1.0.3-alpha 8 | jar 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 1.4.7.RELEASE 14 | 15 | 16 | 17 | 18 | bintray-merapar-maven 19 | bintray 20 | http://dl.bintray.com/merapar/maven 21 | 22 | 23 | 24 | graphql-spring-boot-starter-sample 25 | This is a sample project for the Spring boot starter GraphQL project. 26 | https://github.com/merapar/graphql-spring-boot-starter 27 | 2016 28 | 29 | 30 | 31 | MIT 32 | https://github.com/merapar/graphql-spring-boot-starter/blob/master/LICENSE.md 33 | repo 34 | 35 | 36 | 37 | 38 | 39 | Jan Fockaert 40 | jan.fockaert@merapar.com 41 | Merapar Technologies 42 | https://www.merapartechnologies.com 43 | 44 | 45 | 46 | 47 | scm:git:git://github.com/merapar/graphql-spring-boot-starter.git 48 | scm:git:ssh://github.com:merapar/graphql-spring-boot-starter.git 49 | http://github.com/merapar/graphql-spring-boot-starter/tree/master 50 | 51 | 52 | 53 | UTF-8 54 | UTF-8 55 | 1.8 56 | 57 | 58 | 59 | 60 | 61 | ${project.groupId} 62 | graphql-spring-boot-starter 63 | ${project.version} 64 | 65 | 66 | 67 | 68 | org.projectlombok 69 | lombok 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-web 74 | 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-test 80 | test 81 | 82 | 83 | junit 84 | junit 85 | test 86 | 87 | 88 | org.assertj 89 | assertj-core 90 | test 91 | 92 | 93 | 94 | 95 | 96 | 97 | org.springframework.boot 98 | spring-boot-maven-plugin 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-surefire-plugin 103 | 104 | 105 | org.jacoco 106 | jacoco-maven-plugin 107 | 0.7.7.201606060606 108 | 109 | 110 | 111 | prepare-agent 112 | 113 | 114 | 115 | report 116 | test 117 | 118 | report 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/Application.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | public static void main(String[] args) { 9 | SpringApplication.run(Application.class, args); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/CustomGraphQlExecutorImpl.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample; 2 | 3 | import com.merapar.graphql.executor.GraphQlExecutorImpl; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.Map; 7 | 8 | @Component 9 | public class CustomGraphQlExecutorImpl extends GraphQlExecutorImpl { 10 | @Override 11 | protected void beforeExecuteRequest(String query, String operationName, Map context, Map variables) { 12 | // Integrate own authentication logic 13 | context.put("User", "Username"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/dataFetchers/RoleDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.dataFetchers; 2 | 3 | import com.merapar.graphql.base.TypedValueMap; 4 | import com.merapar.graphql.sample.domain.Role; 5 | import lombok.val; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.*; 9 | 10 | @Component 11 | public class RoleDataFetcher { 12 | 13 | public Map roles = new HashMap<>(); 14 | 15 | public List getRolesByFilter(TypedValueMap arguments) { 16 | Integer id = arguments.get("id"); 17 | 18 | if (id != null) { 19 | return Collections.singletonList(roles.get(id)); 20 | } else { 21 | return new ArrayList<>(roles.values()); 22 | } 23 | } 24 | 25 | public Role addRole(TypedValueMap arguments) { 26 | val role = new Role(); 27 | 28 | role.setId(arguments.get("id")); 29 | role.setName(arguments.get("name")); 30 | 31 | roles.put(role.getId(), role); 32 | 33 | return role; 34 | } 35 | 36 | public Role updateRole(TypedValueMap arguments) { 37 | val role = roles.get(arguments.get("id")); 38 | 39 | if (arguments.containsKey("name")) { 40 | role.setName(arguments.get("name")); 41 | } 42 | 43 | return role; 44 | } 45 | 46 | public Role deleteRole(TypedValueMap arguments) { 47 | val role = roles.get(arguments.get("id")); 48 | 49 | roles.remove(role.getId()); 50 | 51 | return role; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/dataFetchers/UserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.dataFetchers; 2 | 3 | import com.merapar.graphql.base.TypedValueMap; 4 | import com.merapar.graphql.sample.domain.Role; 5 | import com.merapar.graphql.sample.domain.User; 6 | import lombok.val; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.*; 10 | 11 | @Component 12 | public class UserDataFetcher { 13 | 14 | public Map users = new HashMap<>(); 15 | 16 | public List getUsersByFilter(TypedValueMap arguments) { 17 | Integer id = arguments.get("id"); 18 | 19 | if (id != null) { 20 | return Collections.singletonList(users.get(id)); 21 | } else { 22 | return new ArrayList<>(users.values()); 23 | } 24 | } 25 | 26 | public User addUser(TypedValueMap arguments) { 27 | val user = new User(); 28 | 29 | user.setId(arguments.get("id")); 30 | user.setName(arguments.get("name")); 31 | 32 | users.put(user.getId(), user); 33 | 34 | return user; 35 | } 36 | 37 | public User updateUser(TypedValueMap arguments) { 38 | val user = users.get(arguments.get("id")); 39 | 40 | if (arguments.containsKey("name")) { 41 | user.setName(arguments.get("name")); 42 | } 43 | 44 | return user; 45 | } 46 | 47 | public User deleteUser(TypedValueMap arguments) { 48 | val user = users.get(arguments.get("id")); 49 | 50 | users.remove(user.getId()); 51 | 52 | return user; 53 | } 54 | 55 | public List getRoles(User user) { 56 | return Arrays.asList(new Role(1, "Admin")); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/domain/Role.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class Role { 11 | @Getter 12 | @Setter 13 | private Integer id; 14 | 15 | @Getter 16 | @Setter 17 | private String name; 18 | } -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class User { 13 | @Getter 14 | @Setter 15 | private Integer id; 16 | 17 | @Getter 18 | @Setter 19 | private String name; 20 | 21 | private List roles; 22 | } -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/fields/HelloWorldFields.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.fields; 2 | 3 | import com.merapar.graphql.GraphQlFields; 4 | import graphql.schema.GraphQLFieldDefinition; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | import static graphql.Scalars.GraphQLString; 11 | import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; 12 | 13 | @Component 14 | public class HelloWorldFields implements GraphQlFields { 15 | 16 | @Override 17 | public List getQueryFields() { 18 | return Collections.singletonList( 19 | newFieldDefinition() 20 | .type(GraphQLString) 21 | .name("hello") 22 | .staticValue("world") 23 | .build() 24 | ); 25 | } 26 | 27 | @Override 28 | public List getMutationFields() { 29 | return Collections.emptyList(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/fields/RoleFields.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.fields; 2 | 3 | import com.merapar.graphql.GraphQlFields; 4 | import com.merapar.graphql.sample.dataFetchers.RoleDataFetcher; 5 | import graphql.Scalars; 6 | import graphql.schema.*; 7 | import lombok.Getter; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.PostConstruct; 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | import static com.merapar.graphql.base.GraphQlFieldsHelper.*; 17 | import static graphql.Scalars.GraphQLInt; 18 | import static graphql.Scalars.GraphQLString; 19 | import static graphql.schema.GraphQLArgument.newArgument; 20 | import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; 21 | import static graphql.schema.GraphQLInputObjectField.newInputObjectField; 22 | import static graphql.schema.GraphQLInputObjectType.newInputObject; 23 | import static graphql.schema.GraphQLObjectType.newObject; 24 | 25 | @Component 26 | public class RoleFields implements GraphQlFields { 27 | 28 | @Autowired 29 | private RoleDataFetcher roleDataFetcher; 30 | 31 | @Getter 32 | private GraphQLObjectType roleType; 33 | 34 | private GraphQLInputObjectType addRoleInputType; 35 | private GraphQLInputObjectType updateRoleInputType; 36 | private GraphQLInputObjectType deleteRoleInputType; 37 | 38 | private GraphQLInputObjectType filterRoleInputType; 39 | 40 | private GraphQLFieldDefinition rolesField; 41 | private GraphQLFieldDefinition addRoleField; 42 | private GraphQLFieldDefinition updateRoleField; 43 | private GraphQLFieldDefinition deleteRoleField; 44 | 45 | @Getter 46 | private List queryFields; 47 | 48 | @Getter 49 | private List mutationFields; 50 | 51 | @PostConstruct 52 | public void postConstruct() { 53 | createTypes(); 54 | createFields(); 55 | queryFields = Collections.singletonList(rolesField); 56 | mutationFields = Arrays.asList(addRoleField, updateRoleField, deleteRoleField); 57 | } 58 | 59 | private void createTypes() { 60 | roleType = newObject().name("role").description("A role") 61 | .field(newFieldDefinition().name("id").description("The id").type(GraphQLInt).build()) 62 | .field(newFieldDefinition().name("name").description("The name").type(GraphQLString).build()) 63 | .build(); 64 | 65 | addRoleInputType = newInputObject().name("addRoleInput") 66 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 67 | .field(newInputObjectField().name("name").type(new GraphQLNonNull(Scalars.GraphQLString)).build()) 68 | .build(); 69 | 70 | updateRoleInputType = newInputObject().name("updateRoleInput") 71 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 72 | .field(newInputObjectField().name("name").type(GraphQLString).build()) 73 | .build(); 74 | 75 | deleteRoleInputType = newInputObject().name("deleteRoleInput") 76 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 77 | .build(); 78 | 79 | filterRoleInputType = newInputObject().name("filterRoleInput") 80 | .field(newInputObjectField().name("id").type(GraphQLInt).build()) 81 | .build(); 82 | } 83 | 84 | private void createFields() { 85 | rolesField = newFieldDefinition() 86 | .name("roles").description("Provide an overview of all roles") 87 | .type(new GraphQLList(roleType)) 88 | .argument(newArgument().name(FILTER).type(filterRoleInputType).build()) 89 | .dataFetcher(environment -> roleDataFetcher.getRolesByFilter(getFilterMap(environment))) 90 | .build(); 91 | 92 | addRoleField = newFieldDefinition() 93 | .name("addRole").description("Add new role") 94 | .type(roleType) 95 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(addRoleInputType)).build()) 96 | .dataFetcher(environment -> roleDataFetcher.addRole(getInputMap(environment))) 97 | .build(); 98 | 99 | updateRoleField = newFieldDefinition() 100 | .name("updateRole").description("Update existing role") 101 | .type(roleType) 102 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(updateRoleInputType)).build()) 103 | .dataFetcher(environment -> roleDataFetcher.updateRole(getInputMap(environment))) 104 | .build(); 105 | 106 | deleteRoleField = newFieldDefinition() 107 | .name("deleteRole").description("Delete existing role") 108 | .type(roleType) 109 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(deleteRoleInputType)).build()) 110 | .dataFetcher(environment -> roleDataFetcher.deleteRole(getInputMap(environment))) 111 | .build(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /graphql-sample/src/main/java/com/merapar/graphql/sample/fields/UserFields.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.fields; 2 | 3 | import com.merapar.graphql.GraphQlFields; 4 | import com.merapar.graphql.sample.dataFetchers.UserDataFetcher; 5 | import com.merapar.graphql.sample.domain.User; 6 | import graphql.Scalars; 7 | import graphql.schema.*; 8 | import lombok.Getter; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.PostConstruct; 13 | import java.util.Arrays; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | import static com.merapar.graphql.base.GraphQlFieldsHelper.*; 18 | import static graphql.Scalars.GraphQLInt; 19 | import static graphql.Scalars.GraphQLString; 20 | import static graphql.schema.GraphQLArgument.newArgument; 21 | import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; 22 | import static graphql.schema.GraphQLInputObjectField.newInputObjectField; 23 | import static graphql.schema.GraphQLInputObjectType.newInputObject; 24 | import static graphql.schema.GraphQLObjectType.newObject; 25 | 26 | @Component 27 | public class UserFields implements GraphQlFields { 28 | 29 | @Autowired 30 | private UserDataFetcher userDataFetcher; 31 | 32 | @Autowired 33 | private RoleFields roleFields; 34 | 35 | private GraphQLObjectType userType; 36 | 37 | private GraphQLInputObjectType addUserInputType; 38 | private GraphQLInputObjectType updateUserInputType; 39 | private GraphQLInputObjectType deleteUserInputType; 40 | 41 | private GraphQLInputObjectType filterUserInputType; 42 | 43 | private GraphQLFieldDefinition usersField; 44 | private GraphQLFieldDefinition addUserField; 45 | private GraphQLFieldDefinition updateUserField; 46 | private GraphQLFieldDefinition deleteUserField; 47 | 48 | @Getter 49 | private List queryFields; 50 | 51 | @Getter 52 | private List mutationFields; 53 | 54 | @PostConstruct 55 | public void postConstruct() { 56 | createTypes(); 57 | createFields(); 58 | queryFields = Collections.singletonList(usersField); 59 | mutationFields = Arrays.asList(addUserField, updateUserField, deleteUserField); 60 | } 61 | 62 | private void createTypes() { 63 | userType = newObject().name("user").description("A user") 64 | .field(newFieldDefinition().name("id").description("The id").type(GraphQLInt).build()) 65 | .field(newFieldDefinition().name("name").description("The name").type(GraphQLString).build()) 66 | .field(newFieldDefinition().name("roles").description("The roles").type(new GraphQLList(roleFields.getRoleType())) 67 | .dataFetcher(environment -> userDataFetcher.getRoles((User) environment.getSource())) 68 | .build()) 69 | .build(); 70 | 71 | addUserInputType = newInputObject().name("addUserInput") 72 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 73 | .field(newInputObjectField().name("name").type(new GraphQLNonNull(Scalars.GraphQLString)).build()) 74 | .build(); 75 | 76 | updateUserInputType = newInputObject().name("updateUserInput") 77 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 78 | .field(newInputObjectField().name("name").type(GraphQLString).build()) 79 | .build(); 80 | 81 | deleteUserInputType = newInputObject().name("deleteUserInput") 82 | .field(newInputObjectField().name("id").type(new GraphQLNonNull(GraphQLInt)).build()) 83 | .build(); 84 | 85 | filterUserInputType = newInputObject().name("filterUserInput") 86 | .field(newInputObjectField().name("id").type(GraphQLInt).build()) 87 | .build(); 88 | } 89 | 90 | private void createFields() { 91 | usersField = newFieldDefinition() 92 | .name("users").description("Provide an overview of all users") 93 | .type(new GraphQLList(userType)) 94 | .argument(newArgument().name(FILTER).type(filterUserInputType).build()) 95 | .dataFetcher(environment -> userDataFetcher.getUsersByFilter(getFilterMap(environment))) 96 | .build(); 97 | 98 | addUserField = newFieldDefinition() 99 | .name("addUser").description("Add new user") 100 | .type(userType) 101 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(addUserInputType)).build()) 102 | .dataFetcher(environment -> userDataFetcher.addUser(getInputMap(environment))) 103 | .build(); 104 | 105 | updateUserField = newFieldDefinition() 106 | .name("updateUser").description("Update existing user") 107 | .type(userType) 108 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(updateUserInputType)).build()) 109 | .dataFetcher(environment -> userDataFetcher.updateUser(getInputMap(environment))) 110 | .build(); 111 | 112 | deleteUserField = newFieldDefinition() 113 | .name("deleteUser").description("Delete existing user") 114 | .type(userType) 115 | .argument(newArgument().name(INPUT).type(new GraphQLNonNull(deleteUserInputType)).build()) 116 | .dataFetcher(environment -> userDataFetcher.deleteUser(getInputMap(environment))) 117 | .build(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /graphql-sample/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.level.com.merapar=DEBUG -------------------------------------------------------------------------------- /graphql-sample/src/test/java/com/merapar/graphql/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @RunWith(SpringRunner.class) 13 | @SpringBootTest 14 | public class ApplicationTest { 15 | 16 | @Autowired 17 | private ApplicationContext applicationContext; 18 | 19 | @Test 20 | public void testApplicationContext() { 21 | assertThat(applicationContext).isNotNull(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /graphql-sample/src/test/java/com/merapar/graphql/sample/fields/RoleFieldsTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.fields; 2 | 3 | import com.merapar.graphql.controller.GraphQlControllerImpl; 4 | import com.merapar.graphql.sample.dataFetchers.RoleDataFetcher; 5 | import com.merapar.graphql.sample.domain.Role; 6 | import lombok.val; 7 | import org.json.JSONObject; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.test.context.ContextConfiguration; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | import org.springframework.test.web.servlet.ResultActions; 18 | 19 | import java.util.Collections; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 27 | 28 | @RunWith(SpringRunner.class) 29 | @ContextConfiguration(classes = RoleFieldsTestConfiguration.class) 30 | @WebMvcTest(GraphQlControllerImpl.class) 31 | public class RoleFieldsTest { 32 | 33 | @Autowired 34 | private MockMvc mockMvc; 35 | 36 | @Autowired 37 | private RoleDataFetcher roleDataFetcher; 38 | 39 | @Before 40 | public void setup() { 41 | roleDataFetcher.roles = new LinkedHashMap<>(); 42 | roleDataFetcher.roles.put(1, new Role(1, "Admin")); 43 | roleDataFetcher.roles.put(2, new Role(2, "User")); 44 | } 45 | 46 | @Test 47 | public void getRoles() throws Exception { 48 | // Given 49 | val query = "{" + 50 | "roles {" + 51 | " id" + 52 | " name" + 53 | "}}"; 54 | 55 | // When 56 | val postResult = performGraphQlPost(query); 57 | 58 | // Then 59 | postResult.andExpect(status().isOk()) 60 | .andExpect(jsonPath("$.errors").doesNotExist()) 61 | .andExpect(jsonPath("$.data.roles[0].id").value("1")) 62 | .andExpect(jsonPath("$.data.roles[0].name").value("Admin")) 63 | .andExpect(jsonPath("$.data.roles[1].id").value("2")) 64 | .andExpect(jsonPath("$.data.roles[1].name").value("User")); 65 | } 66 | 67 | @Test 68 | public void getRolesById() throws Exception { 69 | // Given 70 | val query = "{" + 71 | "roles(filter: { id: 2 }) {" + 72 | " id" + 73 | " name" + 74 | "}}"; 75 | 76 | // When 77 | val postResult = performGraphQlPost(query); 78 | 79 | // Then 80 | postResult.andExpect(status().isOk()) 81 | .andExpect(jsonPath("$.errors").doesNotExist()) 82 | .andExpect(jsonPath("$.data.roles[0].id").value("2")) 83 | .andExpect(jsonPath("$.data.roles[0].name").value("User")); 84 | } 85 | 86 | @Test 87 | public void addRole() throws Exception { 88 | // Given 89 | val query = "mutation addRole($input: addRoleInput!) {" + 90 | " addRole(input: $input) {" + 91 | " id" + 92 | " name" + 93 | " }\n" + 94 | "}"; 95 | 96 | val variables = new LinkedHashMap<>(); 97 | variables.put("id", 1234); 98 | variables.put("name", "added role"); 99 | 100 | // When 101 | val postResult = performGraphQlPost(query, variables); 102 | 103 | // Then 104 | postResult.andExpect(status().isOk()) 105 | .andExpect(jsonPath("$.errors").doesNotExist()) 106 | .andExpect(jsonPath("$.data.addRole.id").value(1234)) 107 | .andExpect(jsonPath("$.data.addRole.name").value("added role")); 108 | 109 | assertThat(roleDataFetcher.roles).containsKeys(1234); 110 | } 111 | 112 | @Test 113 | public void updateRole() throws Exception { 114 | // Given 115 | val query = "mutation updateRole($input: updateRoleInput!) {" + 116 | " updateRole(input: $input) {" + 117 | " id" + 118 | " name" + 119 | " }\n" + 120 | "}"; 121 | 122 | val variables = new LinkedHashMap<>(); 123 | variables.put("id", 1); 124 | variables.put("name", "updated role"); 125 | 126 | // When 127 | val postResult = performGraphQlPost(query, variables); 128 | 129 | // Then 130 | postResult.andExpect(status().isOk()) 131 | .andExpect(jsonPath("$.errors").doesNotExist()) 132 | .andExpect(jsonPath("$.data.updateRole.id").value(1)) 133 | .andExpect(jsonPath("$.data.updateRole.name").value("updated role")); 134 | 135 | assertThat(roleDataFetcher.roles.get(1).getName()).isEqualTo("updated role"); 136 | } 137 | 138 | @Test 139 | public void deleteRole() throws Exception { 140 | // Given 141 | val query = "mutation deleteRole($input: deleteRoleInput!) {" + 142 | " deleteRole(input: $input) {" + 143 | " id" + 144 | " name" + 145 | " }\n" + 146 | "}"; 147 | 148 | val variables = new LinkedHashMap<>(); 149 | variables.put("id", 2); 150 | 151 | // When 152 | val postResult = performGraphQlPost(query, variables); 153 | 154 | // Then 155 | postResult.andExpect(status().isOk()) 156 | .andExpect(jsonPath("$.errors").doesNotExist()) 157 | .andExpect(jsonPath("$.data.deleteRole.id").value(2)) 158 | .andExpect(jsonPath("$.data.deleteRole.name").value("User")); 159 | 160 | assertThat(roleDataFetcher.roles).containsKeys(1); 161 | } 162 | 163 | private ResultActions performGraphQlPost(String query) throws Exception { 164 | return performGraphQlPost(query, null); 165 | } 166 | 167 | private ResultActions performGraphQlPost(String query, Map variables) throws Exception { 168 | return mockMvc.perform(post("/v1/graphql") 169 | .contentType(MediaType.APPLICATION_JSON) 170 | .content(generateRequest(query, variables)) 171 | ); 172 | } 173 | 174 | private String generateRequest(String query, Map variables) { 175 | val jsonObject = new JSONObject(); 176 | 177 | jsonObject.put("query", query); 178 | 179 | if (variables != null) { 180 | jsonObject.put("variables", Collections.singletonMap("input", variables)); 181 | } 182 | 183 | return jsonObject.toString(); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /graphql-sample/src/test/java/com/merapar/graphql/sample/fields/RoleFieldsTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.fields; 2 | 3 | import com.merapar.graphql.GraphQlAutoConfiguration; 4 | import com.merapar.graphql.sample.dataFetchers.RoleDataFetcher; 5 | import com.merapar.graphql.sample.dataFetchers.UserDataFetcher; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import(GraphQlAutoConfiguration.class) 12 | public class RoleFieldsTestConfiguration { 13 | 14 | @Bean 15 | public RoleDataFetcher roleDataFetcher() { 16 | return new RoleDataFetcher(); 17 | } 18 | 19 | @Bean 20 | public RoleFields roleFields() { 21 | return new RoleFields(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /graphql-sample/src/test/java/com/merapar/graphql/sample/fields/UserFieldsTest.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.fields; 2 | 3 | import com.merapar.graphql.controller.GraphQlControllerImpl; 4 | import com.merapar.graphql.sample.domain.User; 5 | import com.merapar.graphql.sample.dataFetchers.UserDataFetcher; 6 | import lombok.val; 7 | import org.json.JSONObject; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.test.context.ContextConfiguration; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | import org.springframework.test.web.servlet.ResultActions; 18 | 19 | import java.util.Collections; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 26 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 27 | 28 | @RunWith(SpringRunner.class) 29 | @ContextConfiguration(classes = UserFieldsTestConfiguration.class) 30 | @WebMvcTest(GraphQlControllerImpl.class) 31 | public class UserFieldsTest { 32 | 33 | @Autowired 34 | private MockMvc mockMvc; 35 | 36 | @Autowired 37 | private UserDataFetcher userDataFetcher; 38 | 39 | @Before 40 | public void setup() { 41 | userDataFetcher.users = new LinkedHashMap<>(); 42 | userDataFetcher.users.put(1, new User(1, "Jan", Collections.emptyList())); 43 | userDataFetcher.users.put(2, new User(2, "Peter", Collections.emptyList())); 44 | } 45 | 46 | @Test 47 | public void getUsers() throws Exception { 48 | // Given 49 | val query = "{" + 50 | "users {" + 51 | " id" + 52 | " name" + 53 | " roles {" + 54 | " id" + 55 | " name" + 56 | " }" + 57 | "}}"; 58 | 59 | // When 60 | val postResult = performGraphQlPost(query); 61 | 62 | // Then 63 | postResult.andExpect(status().isOk()) 64 | .andExpect(jsonPath("$.errors").doesNotExist()) 65 | .andExpect(jsonPath("$.data.users[0].id").value("1")) 66 | .andExpect(jsonPath("$.data.users[0].name").value("Jan")) 67 | .andExpect(jsonPath("$.data.users[0].roles[0].id").value(1)) 68 | .andExpect(jsonPath("$.data.users[0].roles[0].name").value("Admin")) 69 | .andExpect(jsonPath("$.data.users[1].id").value("2")) 70 | .andExpect(jsonPath("$.data.users[1].name").value("Peter")); 71 | } 72 | 73 | @Test 74 | public void getUsersById() throws Exception { 75 | // Given 76 | val query = "{" + 77 | "users(filter: { id: 2 }) {" + 78 | " id" + 79 | " name" + 80 | "}}"; 81 | 82 | // When 83 | val postResult = performGraphQlPost(query); 84 | 85 | // Then 86 | postResult.andExpect(status().isOk()) 87 | .andExpect(jsonPath("$.errors").doesNotExist()) 88 | .andExpect(jsonPath("$.data.users[0].id").value("2")) 89 | .andExpect(jsonPath("$.data.users[0].name").value("Peter")); 90 | } 91 | 92 | @Test 93 | public void addUser() throws Exception { 94 | // Given 95 | val query = "mutation addUser($input: addUserInput!) {" + 96 | " addUser(input: $input) {" + 97 | " id" + 98 | " name" + 99 | " }\n" + 100 | "}"; 101 | 102 | val variables = new LinkedHashMap<>(); 103 | variables.put("id", 1234); 104 | variables.put("name", "added user"); 105 | 106 | // When 107 | val postResult = performGraphQlPost(query, variables); 108 | 109 | // Then 110 | postResult.andExpect(status().isOk()) 111 | .andExpect(jsonPath("$.errors").doesNotExist()) 112 | .andExpect(jsonPath("$.data.addUser.id").value(1234)) 113 | .andExpect(jsonPath("$.data.addUser.name").value("added user")); 114 | 115 | assertThat(userDataFetcher.users).containsKeys(1234); 116 | } 117 | 118 | @Test 119 | public void updateUser() throws Exception { 120 | // Given 121 | val query = "mutation updateUser($input: updateUserInput!) {" + 122 | " updateUser(input: $input) {" + 123 | " id" + 124 | " name" + 125 | " }\n" + 126 | "}"; 127 | 128 | val variables = new LinkedHashMap<>(); 129 | variables.put("id", 1); 130 | variables.put("name", "updated user"); 131 | 132 | // When 133 | val postResult = performGraphQlPost(query, variables); 134 | 135 | // Then 136 | postResult.andExpect(status().isOk()) 137 | .andExpect(jsonPath("$.errors").doesNotExist()) 138 | .andExpect(jsonPath("$.data.updateUser.id").value(1)) 139 | .andExpect(jsonPath("$.data.updateUser.name").value("updated user")); 140 | 141 | assertThat(userDataFetcher.users.get(1).getName()).isEqualTo("updated user"); 142 | } 143 | 144 | @Test 145 | public void deleteUser() throws Exception { 146 | // Given 147 | val query = "mutation deleteUser($input: deleteUserInput!) {" + 148 | " deleteUser(input: $input) {" + 149 | " id" + 150 | " name" + 151 | " }\n" + 152 | "}"; 153 | 154 | val variables = new LinkedHashMap<>(); 155 | variables.put("id", 2); 156 | 157 | // When 158 | val postResult = performGraphQlPost(query, variables); 159 | 160 | // Then 161 | postResult.andExpect(status().isOk()) 162 | .andExpect(jsonPath("$.errors").doesNotExist()) 163 | .andExpect(jsonPath("$.data.deleteUser.id").value(2)) 164 | .andExpect(jsonPath("$.data.deleteUser.name").value("Peter")); 165 | 166 | assertThat(userDataFetcher.users).containsKeys(1); 167 | } 168 | 169 | private ResultActions performGraphQlPost(String query) throws Exception { 170 | return performGraphQlPost(query, null); 171 | } 172 | 173 | private ResultActions performGraphQlPost(String query, Map variables) throws Exception { 174 | return mockMvc.perform(post("/v1/graphql") 175 | .contentType(MediaType.APPLICATION_JSON) 176 | .content(generateRequest(query, variables)) 177 | ); 178 | } 179 | 180 | private String generateRequest(String query, Map variables) { 181 | val jsonObject = new JSONObject(); 182 | 183 | jsonObject.put("query", query); 184 | 185 | if (variables != null) { 186 | jsonObject.put("variables", Collections.singletonMap("input", variables)); 187 | } 188 | 189 | return jsonObject.toString(); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /graphql-sample/src/test/java/com/merapar/graphql/sample/fields/UserFieldsTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.merapar.graphql.sample.fields; 2 | 3 | import com.merapar.graphql.GraphQlAutoConfiguration; 4 | import com.merapar.graphql.sample.dataFetchers.RoleDataFetcher; 5 | import com.merapar.graphql.sample.dataFetchers.UserDataFetcher; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import(GraphQlAutoConfiguration.class) 12 | public class UserFieldsTestConfiguration { 13 | 14 | @Bean 15 | public UserDataFetcher userDataFetcher() { 16 | return new UserDataFetcher(); 17 | } 18 | 19 | @Bean 20 | public UserFields userFields() { 21 | return new UserFields(); 22 | } 23 | 24 | @Bean 25 | public RoleDataFetcher roleDataFetcher() { 26 | return new RoleDataFetcher(); 27 | } 28 | 29 | @Bean 30 | public RoleFields roleFields() { 31 | return new RoleFields(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.merapar 6 | graphql-spring-boot-starter-parent 7 | 1.0.3-alpha 8 | pom 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 1.4.7.RELEASE 14 | 15 | 16 | graphql-spring-boot-starter-parent 17 | This is a Spring boot starter project for the GraphQL Java project. 18 | https://github.com/merapar/graphql-spring-boot-starter 19 | 2016 20 | 21 | 22 | 23 | MIT 24 | https://github.com/merapar/graphql-spring-boot-starter/blob/master/LICENSE.md 25 | repo 26 | 27 | 28 | 29 | 30 | 31 | Jan Fockaert 32 | jan.fockaert@merapar.com 33 | Merapar Technologies 34 | https://www.merapartechnologies.com 35 | 36 | 37 | 38 | 39 | scm:git:git://github.com/merapar/graphql-spring-boot-starter.git 40 | scm:git:ssh://github.com:merapar/graphql-spring-boot-starter.git 41 | http://github.com/merapar/graphql-spring-boot-starter/tree/master 42 | 43 | 44 | 45 | UTF-8 46 | UTF-8 47 | 1.8 48 | 49 | 50 | 51 | graphql-core 52 | graphql-sample 53 | 54 | 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-surefire-plugin 60 | 61 | 62 | org.jacoco 63 | jacoco-maven-plugin 64 | 0.7.7.201606060606 65 | 66 | 67 | 68 | prepare-agent 69 | 70 | 71 | 72 | report 73 | test 74 | 75 | report 76 | 77 | 78 | 79 | 80 | 81 | maven-resources-plugin 82 | false 83 | 84 | ${basedir}/target/bin 85 | 86 | 87 | src/main/bin 88 | true 89 | 90 | 91 | 92 | 93 | 94 | copy-resources 95 | process-resources 96 | 97 | copy-resources 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/main/bin/bintray.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "name": "graphql-spring-boot-starter", 4 | "repo": "maven", 5 | "subject": "merapar", 6 | "website_url": "https://www.merapartechnologies.com", 7 | "issue_tracker_url": "https://github.com/merapar/graphql-spring-boot-starter/issues", 8 | "vcs_url": "https://github.com/merapar/graphql-spring-boot-starter", 9 | "licenses": ["MIT"], 10 | "public_download_numbers": false, 11 | "public_stats": false 12 | }, 13 | "version": { 14 | "name": "@project.version@", 15 | "vcs_tag": "v@project.version@" 16 | }, 17 | "files": 18 | [ 19 | {"includePattern": "graphql-core/target/(.*.jar)", "uploadPattern": "com/merapar/graphql-spring-boot-starter/@project.version@/$1", "matrixParams": { "override": 1 }}, 20 | {"includePattern": "graphql-core/pom.xml", "uploadPattern": "com/merapar/graphql-spring-boot-starter/@project.version@/graphql-spring-boot-starter-@project.version@.pom", "matrixParams": { "override": 1 }} 21 | ], 22 | "publish": true 23 | } --------------------------------------------------------------------------------